Pārlūkot izejas kodu

Una/modules updates for 15.0.0 rc3 (#5931)

* Modules updates.

* Modules updates.

* Modules updates.

* Modules updates. Messenger.
Anton 1 dienu atpakaļ
vecāks
revīzija
77e90ee275
31 mainītis faili ar 18681 papildinājumiem un 0 dzēšanām
  1. 51 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/install/config.php
  2. 29 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/install/installer.php
  3. 6 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/install/langs/en.xml
  4. 6 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/install/langs/ru.xml
  5. 6 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/install/sql/enable.sql
  6. 3255 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerDb.php
  7. 119 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerLotInfoMenu.php
  8. 169 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerLotMenu.php
  9. 4618 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerModule.php
  10. 1169 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerServices.php
  11. 2979 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerTemplate.php
  12. 128 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/config.php
  13. 444 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/langs/en.xml
  14. 427 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/langs/ru.xml
  15. 16 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/sql/enable-app.sql
  16. 385 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/sql/enable.sql
  17. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/js/emoji/emoji.min.js.map
  18. 4585 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/js/messenger.js
  19. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/attachment.css
  20. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/create-list.css
  21. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/giphy.css
  22. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/history-header.css
  23. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/lot-briefs.css
  24. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/menu-column.css
  25. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/message-menu.css
  26. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/message.css
  27. 278 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/messenger.css
  28. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/talks-list.css
  29. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/text-area.css
  30. 0 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/video-conference.css
  31. 11 0
      modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/images/icons/std-icon.svg

+ 51 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Messenger',
+    'version_from' => '14.0.0',
+    'version_to' => '15.0.0',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/messenger/updates/update_14.0.0_15.0.0/',
+    'home_uri' => 'messenger_update_1400_1500',
+
+    'module_dir' => 'boonex/messenger/',
+    'module_uri' => 'messenger',
+
+    'db_prefix' => 'bx_messenger_',
+    'class_prefix' => 'BxMessenger',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Messenger',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 29 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/install/installer.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxMessengerUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+
+    protected function actionUpdateFiles($bInstall = true)
+    {
+        $iResult = parent::actionUpdateFiles($bInstall);
+
+        if($iResult == BX_DOL_STUDIO_INSTALLER_SUCCESS) {
+            $aModuleConfig = BxDolInstallerUtils::getModuleConfig(BX_DIRECTORY_PATH_MODULES . $this->_aConfig['module_dir'] . 'install/config.php');
+            if(isset($aModuleConfig['note']))
+                $this->oDb->query("UPDATE `sys_modules` SET `note`=:note WHERE `uri`=:uri", [
+                    'note' => $aModuleConfig['note'],
+                    'uri' => $this->_aConfig['module_uri']
+                ]);
+        }
+
+        return $iResult;
+    }
+}

+ 6 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/install/langs/en.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string_add name="_bx_messenger_nav_menu_item_title_favorites"><![CDATA[Favorite chats]]></string_add>
+
+	<string_upd name="_bx_messenger_nav_menu_item_title_saved"><![CDATA[Saved messages]]></string_upd>
+</resources>

+ 6 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/install/langs/ru.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="ru" flag="ru" title="Russian">
+	<string_add name="_bx_messenger_nav_menu_item_title_favorites"><![CDATA[Избранные чаты]]></string_add>
+
+	<string_upd name="_bx_messenger_nav_menu_item_title_saved"><![CDATA[Сохраненные сообщения]]></string_upd>
+</resources>

+ 6 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/install/sql/enable.sql

@@ -0,0 +1,6 @@
+SET @sName = 'bx_messenger';
+
+-- MENUS
+DELETE FROM `sys_menu_items` WHERE `set_name`=CONCAT(@sName, '_nav_menu') AND `module`=@sName AND `name`='favorites';
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES
+(CONCAT(@sName, '_nav_menu'), @sName, 'favorites', '_bx_messenger_nav_menu_item_title_favorites', '_bx_messenger_nav_menu_item_title_favorites', 'javascript:void(0);', '', '_self', 'star col-red2', '', 2147483647, 0, 0, 6);

+ 3255 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerDb.php

@@ -0,0 +1,3255 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup	Messenger Messenger
+ * @ingroup		UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Database queries
+ */
+class BxMessengerDb extends BxBaseModGeneralDb
+{
+    private $CNF;
+    function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+        $this->CNF = &$oConfig->CNF;
+    }
+
+    /**
+     * Get all lot by class name 
+     *@param string $sClass
+     *@return array lot info
+     */
+    public function getLotByClass($sClass)
+    {
+        if (!$sClass)
+            return '';
+
+        $sQuery = $this->prepare("SELECT * FROM `{$this->CNF['TABLE_ENTRIES']}` WHERE `{$this->CNF['FIELD_CLASS']}` = ? LIMIT 1", $sClass);
+        return $this->getRow($sQuery);
+    }
+
+    /**
+     * Get all lot by page url
+     *@param string $sUrl or the page
+     *@return array lot info
+     */
+    public function getLotByUrl($sUrl)
+    {
+        $sQuery = $this->prepare("SELECT * FROM `{$this->CNF['TABLE_ENTRIES']}` WHERE `{$this->CNF['FIELD_URL']}` = ? LIMIT 1", $sUrl);
+        return $this->getRow($sQuery);
+    }
+
+    /**
+     * Get all lot by id
+     *@param int $iId lot id
+     *@return array lot info
+     */
+    public function getLotInfoById($iId)
+    {
+        $sQuery = $this->prepare("SELECT `l`.*, `g`.`{$this->CNF['FMGL_GROUP_ID']}` FROM `{$this->CNF['TABLE_ENTRIES']}` as `l`
+                                           LEFT JOIN `{$this->CNF['TABLE_GROUPS_LOTS']}` as `g` ON `g`.`{$this->CNF['FMGL_LOT_ID']}` = `l`.`{$this->CNF['FIELD_ID']}`
+                                           WHERE `l`.`{$this->CNF['FIELD_ID']}` = ? LIMIT 1", (int) $iId);
+        return $this->getRow($sQuery);
+    }
+
+    /**
+     * Get lot by id or url
+     *@param int $iId lot id
+     *@param string $sUrl url or url of the page with talk 
+     *@param int $iAuthor profile id 
+     *@return array lot info
+     */
+    public function getLotByIdOrUrl($iId, $sUrl, $iAuthorId = 0)
+    {
+        if ($iId)
+            return $this->getLotInfoById($iId);
+
+        if ($sUrl)
+            return $this->getLotByUrl($sUrl);
+
+        return array();
+    }
+
+    /**
+     * Check if is the member author of the lot or jot
+     *@param int $iId lot id
+     *@param int $iAuthor profile id 
+     *@param boolean $bLotAuthor if true checks Lot, otherwise jot  
+     *@return boolean
+     */
+    public function isAuthor($iId, $iAuthorId, $bLotAuthor = true)
+    {
+        if (!$iAuthorId)
+            return false;
+
+        if ($bLotAuthor)
+            $sQuery = $this->prepare("SELECT COUNT(*) FROM `{$this->CNF['TABLE_ENTRIES']}` WHERE `{$this->CNF['FIELD_ID']}` = ? AND `{$this->CNF['FIELD_AUTHOR']}` = ? LIMIT 1", (int) $iId, (int) $iAuthorId);
+        else
+            $sQuery = $this->prepare("SELECT COUNT(*) FROM `{$this->CNF['TABLE_MESSAGES']}` WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = ? AND `{$this->CNF['FIELD_MESSAGE_AUTHOR']}` = ? LIMIT 1", (int) $iId, (int) $iAuthorId);
+
+        return $this->getOne($sQuery) == 1;
+    }
+
+    /**
+     * Deletes lot by lot Id
+     *@param int $iLotId lot id
+     *@return int affected rows
+     */
+    public function deleteLot($iLotId)
+    {
+        $aJots = $this->getJotsByLotId(['lot' => $iLotId]);
+        foreach ($aJots as $iKey => $aJot)
+            $this->clearJotsConnections($aJot[$this->CNF['FIELD_MESSAGE_ID']], $iLotId);
+
+        $this->query("DELETE FROM `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                                        WHERE `{$this->CNF['FIELD_NEW_LOT']}`=:lot                                        
+                                                     ", array('lot' => $iLotId));
+
+        $this->query("DELETE FROM `{$this->CNF['TABLE_ENTRIES']}` WHERE `{$this->CNF['FIELD_ID']}` = :id", array('id' => $iLotId));
+        $this->query("DELETE FROM `{$this->CNF['TABLE_LOT_SETTINGS']}` WHERE `{$this->CNF['FLS_ID']}` = :id", array('id' => $iLotId));
+        $this->query("DELETE FROM `{$this->CNF['TABLE_USERS_INFO']}` WHERE `{$this->CNF['FIELD_INFO_LOT_ID']}` = :id", array('id' => $iLotId));
+        $this->query("DELETE FROM `{$this->CNF['TABLE_MESSAGES']}` WHERE `{$this->CNF['FIELD_MESSAGE_FK']}` = :id", array('id' => $iLotId));
+        $this->query("DELETE FROM `{$this->CNF['TABLE_MASS_TRACKER']}` WHERE `{$this->CNF['FIELD_MASS_CONVO_ID']}` = :id", array('id' => $iLotId));
+
+        $this->removeNotifications(0, $iLotId);
+        return true;
+    }
+
+    /**
+     * Clear lot history
+     *@param int $iLotId lot id
+     *@return boolean result
+     */
+    public function clearLot($iLotId)
+    {
+        $aJots = $this->getJotsByLotId(['lot' => $iLotId]);
+        foreach ($aJots as $iKey => $aJot)
+            $this->clearJotsConnections($aJot[$this->CNF['FIELD_MESSAGE_ID']], $iLotId);
+
+        return $this->query("DELETE FROM `{$this->CNF['TABLE_MESSAGES']}` WHERE `{$this->CNF['FIELD_MESSAGE_FK']}` = :id", array('id' => $iLotId));
+    }
+
+    /**
+     * Deletes jot
+     *@param int iJotId jot id
+     *@param int iProfileId member who deleted jot
+     *@param boolean $bCompletely if true totally remove from the site 
+     *@return int affected rows
+     */
+    public function deleteJot($iJotId, $iProfileId, $bCompletely = false)
+    {
+        $iJotId = (int) $iJotId;
+        if ($bCompletely) {
+            $this->clearJotsConnections($iJotId);
+            return $this->query("DELETE FROM `{$this->CNF['TABLE_MESSAGES']}` WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = :id", array('id' => $iJotId));
+        }
+
+        return $this->query("UPDATE `{$this->CNF['TABLE_MESSAGES']}` 
+								SET 
+									`{$this->CNF['FIELD_MESSAGE_TRASH']}` = 1,
+									`{$this->CNF['FIELD_MESSAGE_LAST_EDIT']}` = UNIX_TIMESTAMP(),
+									`{$this->CNF['FIELD_MESSAGE_EDIT_BY']}` = :profile
+								WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = :id", array('id' => $iJotId, 'profile' => $iProfileId));
+    }
+
+    public function clearJotsConnections($iJotId, $iLotId = 0)
+    {
+        $this->removeFilesByJotId($iJotId);
+        $this->deleteJotReactions($iJotId);
+        $this->deleteSavedJotItems($iJotId);
+
+        if (!$iLotId)
+            $iLotId = $this->getLotByJotId($iJotId, true);
+
+        if ($iLotId) {
+            $aUnreadJots = $this->getAll("SELECT * 
+                                                    FROM `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                                    WHERE `{$this->CNF['FIELD_NEW_JOT']}`<=:jot AND `{$this->CNF['FIELD_NEW_LOT']}`=:lot", array('jot' => $iJotId, 'lot' => $iLotId));
+            foreach ($aUnreadJots as &$aUnreadJot) {
+                $iProfileId = (int) $aUnreadJot[$this->CNF['FIELD_NEW_PROFILE']];
+                $iUnreadJotId = (int) $aUnreadJot[$this->CNF['FIELD_NEW_JOT']];
+                if ($iUnreadJotId && $iUnreadJotId < (int) $iJotId)
+                    $this->query("
+                                           UPDATE `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                           SET `{$this->CNF['FIELD_NEW_UNREAD']}`=`{$this->CNF['FIELD_NEW_UNREAD']}` - 1
+                                           WHERE `{$this->CNF['FIELD_NEW_LOT']}`=:lot AND `{$this->CNF['FIELD_NEW_PROFILE']}`=:profile AND `{$this->CNF['FIELD_NEW_UNREAD']}` > 0 
+                                          ", array('lot' => $iLotId, 'profile' => $iProfileId));
+                else
+                    $this->deleteNewJot($iProfileId, $iLotId, $iJotId);
+            }
+        }
+    }
+
+    public function updateJot($iJotId, $sField, $mixedValue)
+    {
+        return $this->query("UPDATE `{$this->CNF['TABLE_MESSAGES']}` 
+								SET 
+									`{$sField}` =:value
+								WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = :id", array('id' => $iJotId, 'value' => $mixedValue));
+    }
+
+    /**
+     * Removes participant from the participants list
+     *@param int $iLotId lot id
+     *@param int $iParticipant profile id
+     *@return int/false 
+     */
+    public function leaveLot($iLotId, $iParticipant)
+    {
+        $this->deleteNewJot($iParticipant, $iLotId);
+        return $this->removeParticipant($iLotId, $iParticipant);
+    }
+
+    /**
+     * Get Lot(Talk) settings for a member
+     *@param int $iLotId lot id
+     *@param int $iParticipant profile id
+     *@param string $sName name the option
+     *@param boolean $bParams get from params array, otherwise from table's field
+     *@return mixed option value
+     */
+    private function getParams($iLotId, $iParticipant, $sName = 0, $bParams = false)
+    {
+        $sQuery = $this->prepare("SELECT * FROM `{$this->CNF['TABLE_USERS_INFO']}` 
+									WHERE `{$this->CNF['FIELD_INFO_LOT_ID']}` = ? AND `{$this->CNF['FIELD_INFO_USER_ID']}` = ? 
+									LIMIT 1", (int) $iLotId, (int) $iParticipant);
+        $aInfo = $this->getRow($sQuery);
+        if (empty($aInfo))
+            return false;
+
+        if (!$sName)
+            return $aInfo;
+
+        if (!$bParams) {
+            $aParams = unserialize($aInfo[$this->CNF['FIELD_INFO_PARAMS']]);
+            return isset($aParams[$sName]) ? $aParams[$sName] : 0;
+        }
+
+        return $aInfo[$sName];
+    }
+
+    /**
+     * Save Lot(Talk) settings for a member
+     *@param int $iLotId lot id
+     *@param int $iParticipant profile id
+     *@param array $aParams options list
+     *@param boolean $bParams get from  params array otherwise as table field
+     *@return int affected rows
+     */
+    private function setParams($iLotId, $iParticipant, $sName, $sValue, $bParams = false)
+    {
+        $aParams = $this->getParams($iLotId, $iParticipant);
+
+        if (!$bParams) {
+            if (empty($aParams))
+                return $this->query("INSERT INTO `{$this->CNF['TABLE_USERS_INFO']}` SET `{$this->CNF['FIELD_INFO_PARAMS']}` = :values, `{$this->CNF['FIELD_INFO_LOT_ID']}` = :id, `{$this->CNF['FIELD_INFO_USER_ID']}` = :user", array('user' => $iParticipant, 'id' => $iLotId, 'values' => serialize(array($sName => $sValue))));
+
+            return $this->query("UPDATE `{$this->CNF['TABLE_USERS_INFO']}` SET `{$this->CNF['FIELD_INFO_PARAMS']}` = :values WHERE `{$this->CNF['FIELD_INFO_LOT_ID']}` = :id AND `{$this->CNF['FIELD_INFO_USER_ID']}` = :user", array('user' => $iParticipant, 'id' => $iLotId, 'values' => serialize(array($sName => $sValue))));
+        }
+
+        if (empty($aParams))
+            return $this->query("INSERT INTO `{$this->CNF['TABLE_USERS_INFO']}` SET `{$sName}` = :value, `{$this->CNF['FIELD_INFO_LOT_ID']}` = :id, `{$this->CNF['FIELD_INFO_USER_ID']}` = :user", array('user' => $iParticipant, 'id' => $iLotId, 'value' => $sValue));
+
+        return $this->query("UPDATE `{$this->CNF['TABLE_USERS_INFO']}` SET `{$sName}` = :value WHERE `{$this->CNF['FIELD_INFO_LOT_ID']}` = :id AND `{$this->CNF['FIELD_INFO_USER_ID']}` = :user", array('user' => $iParticipant, 'id' => $iLotId, 'value' => $sValue));
+    }
+
+    /**
+     * Make the lot mute for a member
+     *@param int $iLotId lot id
+     *@param int $iParticipant profile id
+     *@return int set value
+     */
+    public function muteLot($iLotId, $iParticipant)
+    {
+        $iCurrentNotification = (int) $this->getParams($iLotId, $iParticipant, 'notification');
+        $iNotification = (int) !$iCurrentNotification;
+
+        $this->setParams($iLotId, $iParticipant, 'notification', $iNotification);
+        return $iNotification;
+    }
+
+    /**
+     * Mark the lot with star
+     *@param int $iLotId lot id
+     *@param int $iParticipant profile id
+     *@return int set
+     */
+    public function starLot($iLotId, $iParticipant)
+    {
+        $iCurrent = (int) $this->getParams($iLotId, $iParticipant, 'star', true);
+        $iNew = (int) !$iCurrent;
+
+        $this->setParams($iLotId, $iParticipant, 'star', $iNew, true);
+        return $iNew;
+    }
+
+    /**
+     * Talks from the list that the member marked with a star
+     *@param int $iProfileId profile id
+     *@param array $aLotIds lots ids
+     *@return array lots ids
+     */
+    public function getStarredLots($iProfileId, $aLotIds)
+    {
+        $aLotIds = array_filter(array_map('intval', (array)$aLotIds));
+        if (!$iProfileId || empty($aLotIds)) {
+            return [];
+        }
+
+        return $this->getColumn("SELECT `{$this->CNF['FIELD_INFO_LOT_ID']}` FROM `{$this->CNF['TABLE_USERS_INFO']}`
+                                    WHERE `{$this->CNF['FIELD_INFO_USER_ID']}` = :profile AND `{$this->CNF['FIELD_INFO_STAR']}` = 1
+                                    AND `{$this->CNF['FIELD_INFO_LOT_ID']}` IN (" . implode(',', $aLotIds) . ")", ['profile' => (int)$iProfileId]);
+    }
+
+    public function isStarred($iLotId, $iParticipant)
+    {
+        return $this->getParams($iLotId, $iParticipant, 'star', true);
+    }
+    /**
+     * Check if the lot is mute for a member
+     *@param int $iLotId lot id
+     *@param int $iParticipant profile id
+     *@return boolean 
+     */
+    public function isMuted($iLotId, $iParticipant)
+    {
+        return $this->getParams($iLotId, $iParticipant, 'notification');
+    }
+
+    /**
+    /**
+    * Save participants list for the lot
+    *@param int $iLotId lot id
+    *@param array $aParticipants
+    *@return int affected rows
+    */
+    public function saveParticipantsList($iLotId, $aParticipants)
+    {
+        $sParticipants = '';
+        if (!empty($aParticipants) && is_array($aParticipants)) {
+            $aParticipants = array_map('intval', $aParticipants);
+            $sParticipants = implode(',', $aParticipants);
+        }
+
+        return $this->query("UPDATE `{$this->CNF['TABLE_ENTRIES']}` SET `{$this->CNF['FIELD_PARTICIPANTS']}` = :parts WHERE `{$this->CNF['FIELD_ID']}` = :id", array('parts' => $sParticipants, 'id' => $iLotId));
+    }
+
+    public function findThePageOwner($mixedPage)
+    {
+        $iAuthorId = 0;
+        if (!$mixedPage)
+            return $iAuthorId;
+
+        if (is_string($mixedPage))
+            parse_str($mixedPage, $aUrl);
+        else if (is_array($mixedPage))
+            $aUrl = $mixedPage;
+
+        if (empty($aUrl) || !isset($aUrl['id']) || !isset($aUrl['i']))
+            return $iAuthorId;
+
+        $sModule = $this->getOne("SELECT `module` FROM `sys_objects_page` WHERE LOWER(`uri`)=:uri", array('uri' => strtolower($aUrl['i'])));
+        if (!$sModule)
+            return $iAuthorId;
+
+        $aTable = $this->getRow("SELECT `TriggerTable`, `TriggerFieldId`, `TriggerFieldAuthor` 
+                                                             FROM `sys_objects_cmts`
+                                                             WHERE `Module`=:module LIMIT 1", array('module' => $sModule));
+        if (empty($aTable))
+            return $iAuthorId;
+
+        if (isset($aTable['TriggerTable']) && isset($aTable['TriggerFieldId']) && isset($aTable['TriggerFieldAuthor']))
+            return $this->getOne(
+                "SELECT `{$aTable['TriggerFieldAuthor']}` 
+                                            FROM `{$aTable['TriggerTable']}` 
+                                            WHERE `{$aTable['TriggerFieldId']}`=:id",
+                array('id' => $aUrl['id'])
+            );
+
+        return $iAuthorId;
+    }
+
+    function createLot($iProfileId, $aData)
+    {
+        $sUrl = isset($aData['url']) ? $aData['url'] : '';
+        $aParticipants = isset($aData['participants']) ? $aData['participants'] : array();
+
+        $iAuthorId = $this->findThePageOwner($sUrl);
+        if (isset($aData['thread']) || !$iAuthorId)
+            $iAuthorId = $iProfileId;
+
+        $iLotId = $this->createNewLot($iAuthorId, $aData, $aParticipants);
+        if (!$iLotId)
+            return false;
+
+        bx_alert($this->_oConfig->getObject('alert'), 'create_lot', $iLotId, $iProfileId, $this->getLotInfoById($iLotId));
+        foreach ($aParticipants as &$iParticipant) {
+            if ((int) $iParticipant === (int) $iProfileId)
+                continue;
+
+            bx_alert($this->_oConfig->getObject('alert'), 'add_part', $iParticipant, $iProfileId, array('lot_id' => $iLotId, 'author_id' => $iAuthorId));
+        }
+
+        return $iLotId;
+    }
+    /**
+     * Save message for participants to database
+     *@param array $aData lot settings
+     *@param array $aParticipants participants list, if it empty then used default for lot
+     *@return int affected rows
+     */
+    public function saveMessage($aData)
+    {
+        $iLotId = isset($aData['lot']) ? (int) $aData['lot'] : 0;
+        $aLotInfo = $iLotId ? $this->getLotInfoById($iLotId) : [];
+        if (empty($aLotInfo) && $aData['type'] == BX_IM_TYPE_PRIVATE && !$aData['title'])
+            $aLotInfo = $this->findLotByParams($aData);
+
+        if ($iLotId && $aData['type'] == BX_IM_TYPE_PRIVATE && !$this->isParticipant($iLotId, $aData['member_id']))
+            return false;
+
+        if (empty($aLotInfo)) {
+            $iLotId = $this->createLot((int) $aData['member_id'], $aData);
+
+            if (isset($aData['group_id']) && (int) $aData['group_id'] && (int) $iLotId)
+                $this->addLotToGroup($iLotId, $aData['group_id']);
+        } else
+            $iLotId = $aLotInfo[$this->CNF['FIELD_ID']];
+
+        if (($aData['type'] != BX_IM_TYPE_PRIVATE || $this->isAuthor($iLotId, $aData['member_id'])) && !$this->isParticipant($iLotId, $aData['member_id'], true)) {
+            if ($this->addMemberToParticipantsList($iLotId, $aData['member_id']))
+                bx_alert(
+                    $this->_oConfig->getObject('alert'),
+                    'participant_joined',
+                    $iLotId,
+                    $aData['member_id'],
+                    array(
+                        'url' => $aData['url'],
+                        'object_author_id' => !empty($aLotInfo[$this->CNF['FIELD_AUTHOR']]) ? $aLotInfo[$this->CNF['FIELD_AUTHOR']] : $this->findThePageOwner($aData['url'])
+                    )
+                );
+        }
+
+        $aData['message'] = clear_xss($aData['message']);
+        return $this->addJot($iLotId, $aData['message'], $aData['member_id']);
+    }
+
+    /**
+     * Find appropriate Talk using input params
+     *@param array $aData params
+     *@return array Lot info
+     */
+    public function findLotByParams($aData)
+    {
+        $sUrl = isset($aData['url']) ? $aData['url'] : '';
+        $aParticipants = isset($aData['participants']) && is_array($aData['participants']) ? $aData['participants'] : [];
+        $sClass = isset($aData['class']) ? $aData['class'] : '';
+        $iType = isset($aData['type']) ? $aData['type'] : 0;
+
+        $aWhere = $aWhereBindings = [];
+        $aLots = [];
+        if (!empty($aParticipants)) {
+            $aItems = $this->getLotsByParticipantsList($aParticipants, $iType, true);
+            foreach ($aItems as &$aItem)
+                $aLots[] = $aItem[$this->CNF['FIELD_ID']];
+
+            if (!empty($aLots))
+                $aWhere[] = "`{$this->CNF['FIELD_ID']}` IN (" . implode(',', $aLots) . ")";
+            else
+                return [];
+        }
+
+        if ($sClass) {
+            $aWhere[] = "`{$this->CNF['FIELD_CLASS']}` =:class";
+            $aWhereBindings['class'] = $sClass;
+        }
+
+        if ($sUrl) {
+            $aWhere[] = "`{$this->CNF['FIELD_URL']}` =:url";
+            $aWhereBindings['url'] = $sUrl;
+        }
+
+        if ($iType) {
+            $aWhere[] = "`{$this->CNF['FIELD_TYPE']}` =:type";
+            $aWhereBindings['type'] = $iType;
+        }
+
+        return $this->getRow("SELECT * FROM `{$this->CNF['TABLE_ENTRIES']}` 
+                                                WHERE " . implode(' AND ', $aWhere) . " LIMIT 1", $aWhereBindings);
+    }
+
+    function getLotsByParticipantsList($aParticipants, $mixedType = false, $bGetAll = false)
+    {
+        $aParticipantsList = $aResult = [];
+        if (empty($aParticipants))
+            return $aResult;
+
+        foreach ($aParticipants as &$iParticipant)
+            $aParticipantsList[] = "(^|,)" . (int) $iParticipant . "(,|$)";
+
+        if (empty($aParticipantsList))
+            return $aResult;
+
+        $aWhere[] = "`{$this->CNF['FIELD_PARTICIPANTS']}` REGEXP '" . implode('|', $aParticipantsList) . "'";
+        if ($mixedType)
+            $aWhere[] = "`{$this->CNF['FIELD_TYPE']}` = " . $mixedType;
+
+        $aLots = $this->getAll("SELECT * FROM `{$this->CNF['TABLE_ENTRIES']}` WHERE " . implode(' AND ', $aWhere) . " ORDER BY `{$this->CNF['FIELD_ID']}` ASC ");
+        if (empty($aLots))
+            return $aResult;
+
+        foreach ($aLots as &$aLot) {
+            $aTalkParticipants = explode(',', $aLot[$this->CNF['FIELD_PARTICIPANTS']]);
+            if (count($aTalkParticipants) != count($aParticipants))
+                continue;
+
+            sort($aTalkParticipants);
+            sort($aParticipants);
+
+            if (array_values($aTalkParticipants) == array_values($aParticipants))
+                $aResult[] = $aLot;
+        }
+
+        return !$bGetAll && !empty($aResult) ? current($aResult) : $aResult;
+    }
+
+    /**
+     * Mark message as read in lot history
+     *@param int $iJotId message id
+     *@param int iProfileId	
+     */
+    public function readMessage($iJotId, $iProfileId)
+    {
+        $aJotInfo = $this->getJotById($iJotId);
+        if (!empty($aJotInfo) && $this->deleteNewJot($iProfileId, $aJotInfo[$this->CNF['FIELD_MESSAGE_FK']], $iJotId))
+            bx_alert($this->_oConfig->getObject('alert'), 'read_jot', $iJotId, $iProfileId, array('author_id' => $aJotInfo[$this->CNF['FIELD_MESSAGE_AUTHOR']], 'lot_id' => $aJotInfo[$this->CNF['FIELD_MESSAGE_FK']]));
+    }
+
+    /**
+     * Mark all message as read in lot history for member
+     * @param int $iLot lot id
+     * @param int iProfileId
+     * @return false
+     */
+    public function readAllMessages($iLot, $iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $aNew = $this->getNewJots($iProfileId, $iLot);
+        if (empty($aNew))
+            return false;
+
+        bx_alert(
+            $this->_oConfig->getObject('alert'),
+            'read_bulk_jot',
+            $iLot,
+            $iProfileId,
+            array(
+                'first_jot' => $aNew[$CNF['FIELD_NEW_JOT']],
+                'count' => $aNew[$CNF['FIELD_NEW_JOT']],
+                'lot_id' => $iLot
+            )
+        );
+
+        $this->deleteNewJot($iProfileId, $iLot);
+        $this->markNotificationAsRead($iProfileId, $iLot);
+    }
+
+    /**
+     *Add new new jot to database  
+     *@param int $iLotID  lot id
+     *@param string $sMessage posted message 
+     *@param int iProfile Id	owner of the message
+     *@return  int affected rows
+     */
+    public function addJot($iLotId, $sMessage, $iProfileId)
+    {
+        $sQuery = $this->prepare("INSERT INTO `{$this->CNF['TABLE_MESSAGES']}` 
+												SET  `{$this->CNF['FIELD_MESSAGE']}` = ?, 
+													 `{$this->CNF['FIELD_MESSAGE_FK']}` = ?, 
+													 `{$this->CNF['FIELD_MESSAGE_AUTHOR']}` = ?,													 
+													 `{$this->CNF['FIELD_MESSAGE_ADDED']}` = UNIX_TIMESTAMP()", $sMessage, $iLotId, $iProfileId);
+
+        if (!$this->query($sQuery))
+            return false;
+
+        $iJotId = $this->lastId();
+        $aParticipants = $this->getParticipantsList($iLotId, true, $iProfileId);
+        $this->markAsNewJot($aParticipants, $iLotId, $iJotId);
+        return $iJotId;
+    }
+
+    /**
+     *Create new chat/lot with list of participants
+     *@param int iProfileId owner of the lot
+     *@param string $sTitle lot title
+     *@param int $iType type of the lot
+     *@param string $sUrl url of the page
+     *@param array $aParticipants list of participants
+     *@return  int affected rows
+     */
+    public function createNewLot($iProfileId, $aData, $aParticipants = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // before to create lot
+        bx_alert($this->_oConfig->getObject('alert'), 'create_lot_before', 0, $iProfileId, array(
+            'data' => &$aData,
+            'participants' => &$aParticipants
+        ));
+
+        $sTitle = isset($aData['title']) ? $aData['title'] : '';
+        $iType = isset($aData['type']) ? (int) $aData['type'] : BX_IM_TYPE_PRIVATE;
+        $sUrl = isset($aData['url']) ? $aData['url'] : '';
+        $sPage = isset($aData['page']) ? $aData['page'] : '';
+        $sClass = isset($aData['class']) ? $aData['class'] : BX_MSG_TALK_CLASS_CUSTOM;
+        $iThread = isset($aData['thread']) ? (int) $aData['thread'] : BX_ATT_TYPE_CUSTOM;
+        $iVisibility = isset($aData['visibility']) ? (int) $aData['visibility'] : 0;
+
+        $sHash = $this->_oConfig->generateConvoHash(time());
+        $mixedParticipants = !empty($aParticipants) ? implode(',', $aParticipants) : '';
+
+        $sQuery = $this->prepare(
+            "INSERT INTO `{$this->CNF['TABLE_ENTRIES']}` 
+										SET  `{$this->CNF['FIELD_TITLE']}` = ?,
+													 `{$this->CNF['FIELD_TYPE']}` = ?,
+													 `{$this->CNF['FIELD_AUTHOR']}` = ?,
+													 `{$this->CNF['FIELD_ADDED']}` = UNIX_TIMESTAMP(),
+													 `{$this->CNF['FIELD_PARTICIPANTS']}` = ?,
+													 `{$this->CNF['FIELD_VISIBILITY']}` = ?,
+													 `{$this->CNF['FIELD_CLASS']}` = ?,
+													 `{$this->CNF['FIELD_PARENT_JOT']}` = ?,
+													 `{$this->CNF['FIELD_URL']}` = ?,
+													 `{$this->CNF['FIELD_HASH']}` = ?",
+            $sTitle,
+            $iType,
+            $iProfileId,
+            $mixedParticipants,
+            $iVisibility,
+            $sClass,
+            $iThread,
+            $sUrl,
+            $sHash
+        );
+
+        return $this->query($sQuery) ? $this->lastId() : false;
+    }
+
+    /**
+     *Get list of types of member lots
+     *@param int iProfileId
+     *@param string $sParam keyword to filter lots
+     *@return  array found lots types with lots count per each type
+     */
+    public function getMemberLotsTypes($iProfileId, $sParam = '')
+    {
+        $sWhere = '';
+        $aWhere = array();
+
+        if ($iProfileId) {
+            $aSWhere[] = "FIND_IN_SET(:profile, `{$this->CNF['FIELD_PARTICIPANTS']}`)";
+            $aWhere = array('profile' => $iProfileId);
+        }
+
+        if ($sParam) {
+            $sParam = "%{$sParam}%";
+            $aSWhere[] = " (`{$this->CNF['FIELD_TITLE']}` LIKE :title OR `{$this->CNF['FIELD_URL']}` LIKE :url OR `{$this->CNF['FIELD_TYPE']}` LIKE :type)";
+            $aWhere = array_merge($aWhere, array('title' => $sParam, 'url' => $sParam, 'type' => $sParam));
+        }
+
+        if (!empty($aWhere))
+            $sWhere = "WHERE (" . implode(' AND ', $aSWhere) . ')';
+
+        return $this->getPairs("SELECT `{$this->CNF['FIELD_TYPE']}`, COUNT(*) as `count` 
+			FROM `{$this->CNF['TABLE_ENTRIES']}` 
+			{$sWhere}
+			GROUP BY `{$this->CNF['FIELD_TYPE']}`", $this->CNF['FIELD_TYPE'], 'count', $aWhere);
+    }
+
+    /**
+     *Get list of all existed types 
+     *@return  array types
+     */
+    public function getAllLotsTypes()
+    {
+        return $this->getAll("SELECT * FROM `{$this->CNF['TABLE_TYPES']}` ORDER BY `{$this->CNF['FIELD_TYPE_ID']}` ASC");
+    }
+
+    /**
+     *Get list of all existed types as pairs: Id - Value
+     *@return  array types
+     */
+    public function getLotsTypesPairs()
+    {
+        return $this->getPairs("SELECT * FROM `{$this->CNF['TABLE_TYPES']}` ORDER BY `{$this->CNF['FIELD_TYPE_ID']}`", $this->CNF['FIELD_TYPE_ID'], $this->CNF['FIELD_TYPE_NAME']);
+    }
+
+    public function getLotTypeId($sType)
+    {
+        return $this->getOne("SELECT `{$this->CNF['FIELD_TYPE_ID']}` FROM `{$this->CNF['TABLE_TYPES']}` WHERE  `{$this->CNF['FIELD_TYPE_NAME']}`=:name LIMIT 1", ['name' => $sType]);
+    }
+
+    /**
+     * Check if this ype of lot exists
+     *@param int $iType type of the lot
+     *@return boolean 
+     */
+    public function isLotType($iType)
+    {
+        return $this->getOne("SELECT COUNT(*) FROM `{$this->CNF['TABLE_TYPES']}` WHERE `{$this->CNF['FIELD_TYPE_ID']}` = :type LIMIT 1", array('type' => $iType)) == 1;
+    }
+
+    /**
+     * Get lot's participant list 
+     *@param int $iLotId lot id
+     *@param boolean $bArray if true, returns result as array, otherwise string with ids separated commas
+     *@param int $bExcludeProfile allows to exclude profile from the list, usually it is owner
+     *@return string/array
+     */
+    public function getParticipantsList($iLotId, $bArray = true, $bExcludeProfile = 0)
+    {
+        $sParticipants = $this->getOne("SELECT `{$this->CNF['FIELD_PARTICIPANTS']}` FROM `{$this->CNF['TABLE_ENTRIES']}` WHERE `{$this->CNF['FIELD_ID']}` = :value", array('value' => $iLotId));
+
+        if (!$sParticipants)
+            return [];
+
+        $aParticipants = explode(',', $sParticipants);
+        if ($bExcludeProfile && ($iId = array_search($bExcludeProfile, $aParticipants)) !== FALSE)
+            unset($aParticipants[$iId]);
+
+        return !$bArray ? implode(',', $aParticipants) : $aParticipants;
+    }
+
+    /**
+     * Check if it is participant of the lot
+     *@param int $iLotId lot id
+     *@param int $iParticipantId profile id
+     *@param boolean $bAsParticipant if true to check only participants list without lot owner
+     *@return boolean 
+     */
+    public function isParticipant($iLotId, $iParticipantId, $bAsParticipant = false)
+    {
+        $aParticipants = $this->getParticipantsList($iLotId);
+        if (array_search($iParticipantId, $aParticipants) !== FALSE)
+            return true;
+
+        $aLotInfo = $this->getLotInfoById($iLotId);
+        if (!empty($aLotInfo) && $aLotInfo[$this->CNF['FIELD_TYPE']] == BX_IM_TYPE_BROADCAST)
+            return $this->isBroadcastParticipant($iLotId, $iParticipantId);
+
+        return !$bAsParticipant ? $this->getOne("SELECT COUNT(*) FROM `{$this->CNF['TABLE_ENTRIES']}` WHERE `{$this->CNF['FIELD_ID']}` = :lot AND `{$this->CNF['FIELD_AUTHOR']}` = :author LIMIT 1", array('lot' => (int) $iLotId, 'author' => (int) $iParticipantId)) == 1 : false;
+    }
+
+    /**
+     * Add profile to lot's participants list
+     *@param int $iLotId lot id
+     *@param int $iParticipantId profile id
+     *@return int affected rows
+     */
+    public function addMemberToParticipantsList($iLotId, $iParticipantId)
+    {
+        $sParticipants = $this->getParticipantsList($iLotId, false /* as string list */);
+        $sParticipants = $sParticipants ? "{$sParticipants},{$iParticipantId}" : $iParticipantId;
+        return $this->query("UPDATE `{$this->CNF['TABLE_ENTRIES']}` SET `{$this->CNF['FIELD_PARTICIPANTS']}` = :parts WHERE `{$this->CNF['FIELD_ID']}` = :id", array('parts' => $sParticipants, 'id' => $iLotId));
+    }
+
+    /**
+     * Remove profile from participants list
+     *@param int $iLotId lot id
+     *@param int $iParticipantId profile id
+     *@return mixed false or affected rows
+     */
+    public function removeParticipant($iLotId, $iParticipantId)
+    {
+        $aParticipants = $this->getParticipantsList($iLotId);
+        $iKey = array_search($iParticipantId, $aParticipants);
+
+        if ($iKey !== FALSE) {
+            unset($aParticipants[$iKey]);
+            return $this->saveParticipantsList($iLotId, $aParticipants);
+        }
+
+        return false;
+    }
+
+    /**
+     * Get jots list for specified lot
+     * @param $aData
+     * @return array of the jots
+     */
+    public function getJotsByLotId($aData)
+    {  /* $iLotId, $iStart = BX_IM_EMPTY, $sMode = 'new', $iLimit = BX_IM_EMPTY, $bInclude = false */
+        $iLotId = isset($aData['lot']) ? (int)$aData['lot'] : BX_IM_EMPTY;
+        $iStart = isset($aData['start']) ? (int)$aData['start'] : BX_IM_EMPTY;
+        $sMode = $aData['mode'] ?? 'new';
+        $iLimit = isset($aData['limit']) ? (int) $aData['limit'] : BX_IM_EMPTY;
+        $bInclude = isset($aData['include']) && $aData['include'];
+        $sArea = $aData['area'] ?? '';
+
+        $sJoin = $sWhereJoin = $sLimit = '';
+        $aSWhere[] = "`m`.`{$this->CNF['FIELD_MESSAGE_FK']}` = :lot_id ";
+        $aBindings['lot_id'] = (int) $iLotId;
+        $sInsideOrder = $sMode == 'all' ? 'ASC' : 'DESC';
+
+        if ($iStart && $sMode !== 'all') {
+            $sEqual = '';
+            if ($bInclude)
+                $sEqual = '=';
+
+            $aSWhere[] = "`m`.`{$this->CNF['FIELD_MESSAGE_ID']}` " . ($sMode == 'new' ? '>' . $sEqual : '<' . $sEqual) . " :start ";
+            $aBindings['start'] = (int) $iStart;
+
+            if ($sMode == 'new')
+                $sInsideOrder = 'ASC';
+        }
+
+        if ($iLimit) {
+            $sLimit = "LIMIT :limit";
+            $aBindings['limit'] = (int) $iLimit;
+        }
+
+        if (!empty($aBindings))
+            $sWhere = 'WHERE ' . implode(' AND ', $aSWhere);
+
+        if ($sArea)
+            list($sJoin, $sWhereJoin, $sFields, $sGroupBy) = $this->getJotsByArea($sArea);
+
+        $sQuery = "SELECT `m`.* FROM `{$this->CNF['TABLE_MESSAGES']}` as `m`
+                    {$sJoin}
+			    	{$sWhere} {$sWhereJoin}	
+				    ORDER BY `m`.`{$this->CNF['FIELD_MESSAGE_ID']}` {$sInsideOrder}
+					{$sLimit}";
+
+        return $this->getAll($iStart && $sMode == 'new' ? $sQuery : "({$sQuery}) ORDER BY `{$this->CNF['FIELD_MESSAGE_ID']}`", $aBindings);
+    }
+
+    function getJotsByArea($sArea)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sGroupBy = $sJoin = $sWhere = $sFields = '';
+        $iProfileId = bx_get_logged_profile_id();
+        switch ($sArea) {
+            case BX_MSG_TALK_TYPE_MR:
+                $sUrl = BxDolProfile::getInstance($iProfileId)->getUrl();
+                $sJoin = "LEFT JOIN `{$CNF['TABLE_JOT_REACTIONS']}` as `r` ON `m`.`{$CNF['FIELD_MESSAGE_ID']}` = `r`.`{$CNF['FIELD_REACT_JOT_ID']}`";
+                $sWhere = "AND ((`r`.`{$CNF['FIELD_REACT_JOT_ID']}` IS NOT NULL AND `m`.`{$CNF['FIELD_MESSAGE_AUTHOR']}`= '{$iProfileId}') OR `m`.`{$CNF['FIELD_MESSAGE']}` LIKE '\"%" . '/' . bx_ltrim_str($sUrl, BX_DOL_URL_ROOT) . "\"')";
+                break;
+            case BX_MSG_TALK_TYPE_THREADS:
+                break;
+            case BX_MSG_TALK_TYPE_SAVED:
+                $sJoin = "INNER JOIN `{$CNF['TABLE_SAVED_JOTS']}` as `s` ON `s`.`{$CNF['FSJ_ID']}` = `m`.`{$CNF['FIELD_MESSAGE_ID']}` AND `s`.`{$CNF['FSJ_PROFILE_ID']}` = " . (int)$iProfileId;
+                $sWhere = "AND `s`.`{$CNF['FSJ_ID']}` IS NOT NULL";
+                break;
+            case BX_MSG_TALK_TYPE_REPLIES:
+                $sJoin = "LEFT JOIN `{$CNF['TABLE_MESSAGES']}` as `r` ON `r`.`{$CNF['FIELD_MESSAGE_ID']}` = `m`.`{$CNF['FIELD_MESSAGE_REPLY']}`";
+                $sWhere = "AND `m`.`{$CNF['FIELD_MESSAGE_REPLY']}` <> 0 AND `r`.`{$CNF['FIELD_MESSAGE_AUTHOR']}`={$iProfileId}";
+        }
+
+        return [$sJoin, $sWhere, $sFields, $sGroupBy];
+    }
+
+    public function getUnreadMessages($iProfileId, $iLotId, $mixedLimit = 10)
+    {
+        $sWhere = "WHERE `{$this->CNF['FIELD_MESSAGE_FK']}` = :lot_id";
+        $aWhere = array('lot_id' => (int) $iLotId);
+
+        $aLotInfo = $this->getNewJots($iProfileId, $iLotId);
+        if (empty($aLotInfo))
+            return false;
+
+        $sWhere .= " AND `{$this->CNF['FIELD_MESSAGE_ID']}` > :jot";
+        $aWhere['jot'] = $aLotInfo[$this->CNF['FIELD_NEW_JOT']];
+
+        $sLimit = '';
+        if ($mixedLimit)
+            $sLimit = "LIMIT " . (int) $mixedLimit;
+
+        $sQuery = "SELECT * FROM `{$this->CNF['TABLE_MESSAGES']}`
+									{$sWhere}
+									ORDER BY `{$this->CNF['FIELD_MESSAGE_ID']}`
+									{$sLimit}";
+
+        return $this->getAll($sQuery, $aWhere);
+    }
+
+    public function getPrevJot($iLotId, $iStart)
+    {
+        $sWhere = "WHERE `{$this->CNF['FIELD_MESSAGE_FK']}` = :lot_id";
+        $aWhere = array('lot_id' => (int) $iLotId);
+        if ($iStart) {
+            $sWhere .= " AND `{$this->CNF['FIELD_MESSAGE_ID']}` < :jot";
+            $aWhere['jot'] = $iStart;
+        }
+
+        $sQuery = "SELECT `{$this->CNF['FIELD_MESSAGE_ID']}` FROM `{$this->CNF['TABLE_MESSAGES']}`
+									{$sWhere}
+									ORDER BY `{$this->CNF['FIELD_MESSAGE_ID']}` DESC
+									LIMIT 1";
+
+        return $this->getOne($sQuery, $aWhere);
+    }
+
+    public function getIntervalJotsCount($iLotId, $iJotId)
+    {
+        $sQuery = "SELECT COUNT(*) 
+                        FROM `{$this->CNF['TABLE_MESSAGES']}`
+						WHERE `{$this->CNF['FIELD_MESSAGE_FK']}`=:lot_id 
+						        AND `{$this->CNF['FIELD_MESSAGE_ADDED']}` > (UNIX_TIMESTAMP() - :interval)
+						        AND `{$this->CNF['FIELD_MESSAGE_ID']}` < :jot";
+
+        return $this->getOne($sQuery, array('lot_id' => (int) $iLotId, 'interval' => $this->CNF['PARAM_MESSAGES_INTERVAL'] * 60, 'jot' => (int) $iJotId));
+    }
+
+    function getLotJotsCount($iLotId)
+    {
+        if (!$iLotId)
+            return false;
+
+        // find the user with most number of unread messages
+        $iUnread = $this->getOne("SELECT `{$this->CNF['FIELD_NEW_UNREAD']}` FROM `{$this->CNF['TABLE_NEW_MESSAGES']}`
+                                      WHERE `{$this->CNF['FIELD_NEW_LOT']}`=:id
+                                      ORDER BY `{$this->CNF['FIELD_NEW_UNREAD']}` DESC
+                                      LIMIT 1", array('id' => $iLotId));
+
+        $iTotal = $this->getOne("SELECT COUNT(*)
+                                             FROM `{$this->CNF['TABLE_MESSAGES']}`                                             
+                                             WHERE `{$this->CNF['FIELD_MESSAGE_FK']}`=:id 
+                                             ", array('id' => $iLotId));
+
+        return array('unread' => $iUnread, 'read' => ($iTotal - $iUnread));
+    }
+    /**
+     * Get lot id by jot id 
+     *@param int $iJotId jot id
+     *@param boolean bIdOnly return id or array with info
+     *@return mixed lot id or lot info in array
+     */
+    public function getLotByJotId($iJotId, $bIdOnly = true)
+    {
+        $iLotId = $this->getOne("SELECT `{$this->CNF['FIELD_MESSAGE_FK']}`
+						FROM `{$this->CNF['TABLE_MESSAGES']}` 
+						WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = :jot LIMIT 1", array('jot' => $iJotId));
+
+        if (!$iLotId)
+            return false;
+
+        return $bIdOnly ? $iLotId : $this->getRow("SELECT * FROM `{$this->CNF['TABLE_ENTRIES']}` 
+						WHERE `{$this->CNF['FIELD_ID']}` = :lot LIMIT 1", array('lot' => $iLotId));
+    }
+
+    public function getLotByJotPairs(&$aJotsList)
+    {
+        $aJotsList = array_map('intval', $aJotsList);
+        return !empty($aJotsList) ? $this->getAll("SELECT `{$this->CNF['FIELD_MESSAGE_FK']}`, `{$this->CNF['FIELD_MESSAGE_ID']}` 
+                                                               FROM `{$this->CNF['TABLE_MESSAGES']}` 
+                                                               WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` IN (" . implode(',', $aJotsList) . ")") : false;
+    }
+
+    /**
+     * Get lot id by parent id 
+     *@param int $iJotId jot id	
+     *@return mixed lot id or lot info in array
+     */
+    public function getLotByParentId($iParentId)
+    {
+        return $this->getRow("SELECT * FROM `{$this->CNF['TABLE_ENTRIES']}`
+						WHERE `{$this->CNF['FIELD_PARENT_JOT']}`=:id LIMIT 1", array('id' => $iParentId));
+    }
+
+    /**
+     * Get the latest posted jot(message)
+     *@param int $iLotId lot id
+     *@param int $iProfileId if not specified then just latest jot of any member
+     *@param boolean $bNotTrash don't show trash message
+     *@return array with jot info
+     */
+    public function getLatestJot($iLotId, $iProfileId = 0, $bNotTrash = true)
+    {
+        $sWhere = '';
+        $aWhere['lot'] = $iLotId;
+
+        if ($iProfileId) {
+            $sWhere = " AND `{$this->CNF['FIELD_MESSAGE_AUTHOR']}` = :profile";
+            $aWhere['profile'] = $iProfileId;
+        }
+
+        if ($bNotTrash)
+            $sWhere .= " AND `{$this->CNF['FIELD_MESSAGE_TRASH']}` = 0";
+
+        return $this->getRow("SELECT *
+			FROM `{$this->CNF['TABLE_MESSAGES']}` 
+			WHERE  `{$this->CNF['FIELD_MESSAGE_FK']}` = :lot {$sWhere}
+			ORDER BY `{$this->CNF['FIELD_MESSAGE_ID']}` DESC
+			LIMIT 1", $aWhere);
+    }
+
+    public function getLatestJotsAuthors($iLotId, $iExcludeProfileId = 0, $iLimit = 3)
+    {
+        $sWhere = '';
+        $aWhere = ['lot' => $iLotId, 'limit' => $iLimit];
+
+        if ($iExcludeProfileId) {
+            $sWhere = " AND `{$this->CNF['FIELD_MESSAGE_ID']}` != :profile";
+            $aWhere['profile'] = (int) $iExcludeProfileId;
+        }
+
+        return $this->getPairs("SELECT `{$this->CNF['FIELD_MESSAGE_AUTHOR']}`, MAX(`{$this->CNF['FIELD_MESSAGE_ADDED']}`) as `{$this->CNF['FIELD_MESSAGE_ADDED']}`
+			FROM `{$this->CNF['TABLE_MESSAGES']}`
+			WHERE  `{$this->CNF['FIELD_MESSAGE_FK']}` = :lot {$sWhere}
+            GROUP BY `{$this->CNF['FIELD_MESSAGE_AUTHOR']}`   
+			ORDER BY `{$this->CNF['FIELD_MESSAGE_ADDED']}` DESC
+			LIMIT :limit", $this->CNF['FIELD_MESSAGE_ADDED'], $this->CNF['FIELD_MESSAGE_AUTHOR'], $aWhere);
+    }
+
+    /**
+     * Get jot(message) by id
+     *@param int $iJotId id
+     *@return array with jot info
+     */
+    public function getJotById($iJotId)
+    {
+        $sQuery = $this->prepare("SELECT * FROM `{$this->CNF['TABLE_MESSAGES']}` WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = ?", $iJotId);
+        return $this->getRow($sQuery);
+    }
+
+    /**
+     * Get messages number between two messages
+     * @param $iLotId
+     * @param $iJotStart
+     * @param $iJotEnd
+     * @return array with jot info
+     */
+    public function getJotsNumber($iLotId, $iJotStart, $iJotEnd = 0)
+    {
+        $aWhere = array('id' => $iLotId, 'start' => $iJotStart);
+        $sWhere = "`{$this->CNF['FIELD_MESSAGE_ID']}` > :start";
+        if ($iJotEnd) {
+            $sWhere = "`{$this->CNF['FIELD_MESSAGE_ID']}` BETWEEN :start AND :end";
+            $aWhere['end'] = $iJotEnd;
+        }
+
+        return $this->getOne("SELECT COUNT(*) FROM `{$this->CNF['TABLE_MESSAGES']}` 
+                            WHERE `{$this->CNF['FIELD_MESSAGE_FK']}`=:id AND {$sWhere} 
+                            ORDER BY `{$this->CNF['FIELD_MESSAGE_ID']}` ASC
+                            ", $aWhere);
+    }
+
+    /**
+     * Get all pages with comments block	
+     *@return array list with pages
+     */
+    public function getPagesWithComments()
+    {
+        return $this->getPairs("SELECT * FROM `sys_objects_cmts`", 'Name', 'BaseUrl');
+    }
+
+    /**
+     * Check if the page already contains messenger block
+     *@param $sPage page name 
+     *@return boolean
+     */
+    public function isBlockAdded($sPage)
+    {
+        $sPage = bx_process_input($sPage);
+        $sQuery = $this->prepare("SELECT COUNT(*) FROM `sys_pages_blocks` WHERE `object` = ? AND `title` = '_bx_messenger_page_block_title_messenger'", $sPage);
+
+        return $this->getOne($sQuery) == 1;
+    }
+
+    /**
+     * Add messenger block to the page
+     *@param $sPage page name
+     *@return affected rows
+     */
+    public function addMessengerBlock($sPage)
+    {
+        $sPage = bx_process_input($sPage);
+        $aInfo = $this->findCommentsBlock($sPage);
+
+        if (!empty($aInfo))
+            return $this->query("INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`, `active`) VALUES
+							  (:page, :cell, 'bx_messenger', '_bx_messenger_page_block_title_messenger', 0, 2147483647, 'service', 'a:3:{s:6:\"module\";s:12:\"bx_messenger\";s:6:\"method\";s:19:\"get_block_messenger\";s:6:\"params\";a:1:{i:0;s:6:\"{type}\";}}', 0, 0, :order, 0)",
+                array('page' => $sPage, 'cell' => $aInfo['cell_id'], 'order' => $aInfo['order'] + 1)
+            );
+
+        return $this->query("INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`, `active`) VALUES
+							  (:page, 1, 'bx_messenger', '_bx_messenger_page_block_title_messenger', 0, 2147483647, 'service', 'a:3:{s:6:\"module\";s:12:\"bx_messenger\";s:6:\"method\";s:19:\"get_block_messenger\";s:6:\"params\";a:1:{i:0;s:6:\"{type}\";}}', 0, 0, 0, 0)",
+            array('page' => $sPage)
+        );
+    }
+
+    /**
+     * Find comments block on the page
+     *@param $sPage page name
+     *@return array block info
+     */
+    private function findCommentsBlock($sPage)
+    {
+        $sPage = bx_process_input($sPage);
+
+        $sQuery = $this->prepare("SELECT * FROM `sys_pages_blocks` WHERE `object` = ? AND `title` LIKE '%comments%'", $sPage);
+        return $this->getRow($sQuery);
+    }
+
+    public function getUnreadJotsMessagesCount($iProfileId, $iLotId, $iParentId = 0)
+    {
+        if (!(int) $iLotId)
+            return false;
+
+        return $this->getNewJots($iProfileId, $iLotId, true);
+    }
+
+    function getLotsWithUnreadMessages($iProfileId)
+    {
+        if (!$iProfileId)
+            return false;
+
+        return $this->getPairs("SELECT `{$this->CNF['FIELD_NEW_UNREAD']}`, `{$this->CNF['FIELD_NEW_LOT']}` 
+                                      FROM `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                      WHERE `{$this->CNF['FIELD_NEW_PROFILE']}`=:profile AND `{$this->CNF['FIELD_NEW_UNREAD']}` > 0                                       
+                                      ", $this->CNF['FIELD_NEW_LOT'], $this->CNF['FIELD_NEW_UNREAD'], array('profile' => $iProfileId));
+    }
+
+    function getNewJots($iProfileId, $iLotId, $bCount = false)
+    {
+        $aInfo = $this->getRow("SELECT * 
+                                           FROM `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                           WHERE `{$this->CNF['FIELD_NEW_PROFILE']}`=:profile AND `{$this->CNF['FIELD_NEW_LOT']}`=:lot
+                                          ", array('lot' => $iLotId, 'profile' => $iProfileId));
+        if (empty($aInfo))
+            return 0;
+
+        return $bCount ? (int) $aInfo[$this->CNF['FIELD_NEW_UNREAD']] : $aInfo;
+    }
+
+    function getForWhomJotIsNew($iLotId, $iJotId)
+    {
+        if (!$iLotId && !$iJotId)
+            return [];
+
+        return $this->getColumn("SELECT `{$this->CNF['FIELD_NEW_PROFILE']}` 
+                                            FROM `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                            WHERE `{$this->CNF['FIELD_NEW_JOT']}` <=:jot AND `{$this->CNF['FIELD_NEW_LOT']}`=:lot
+                                         ", array('jot' => $iJotId, 'lot' => $iLotId));
+    }
+
+    private function addNewJotItem($iProfileId, $iLotId, $iJotId)
+    {
+        if (!(int) $iProfileId)
+            return false;
+
+        $iJotNumber = 0;
+        $iLastJot = $iJotId;
+        $aJotInfo = $this->getNewJots($iProfileId, $iLotId);
+        if (!empty($aJotInfo)) {
+            $iJotNumber = (int) $aJotInfo[$this->CNF['FIELD_NEW_UNREAD']];
+            $iLastJot = (int) $aJotInfo[$this->CNF['FIELD_NEW_JOT']];
+        }
+
+        return $this->query("REPLACE INTO `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                            SET 
+                                            `{$this->CNF['FIELD_NEW_PROFILE']}`=:profile,
+                                            `{$this->CNF['FIELD_NEW_UNREAD']}`=:number,
+                                            `{$this->CNF['FIELD_NEW_LOT']}`=:lot,
+                                            `{$this->CNF['FIELD_NEW_JOT']}`=:jot
+                                         ", array(
+            'profile' => $iProfileId,
+            'lot' => $iLotId,
+            'number' => $iJotNumber + 1,
+            'jot' => $iLastJot
+        ));
+    }
+
+    private function addNewJotItems($aProfileIds, $iLotId, $iJotId)
+    {
+        $aProfileIds = array_values(
+            array_unique(
+                array_filter($aProfileIds, fn($iProfileId) => $iProfileId > 0)
+            )
+        );
+
+        if (empty($aProfileIds))
+            return false;
+
+        $bExecuted = false;
+        foreach (array_chunk($aProfileIds, 200) as $aChunk) {
+            $aValues = array_map(function ($iProfileId) use ($iLotId, $iJotId) {
+                return sprintf("(%d, %d, 1, %d)", (int) $iLotId, (int) $iJotId, (int) $iProfileId);
+            }, $aChunk);
+
+            $bExecuted |= (bool) $this->query("
+                INSERT INTO `{$this->CNF['TABLE_NEW_MESSAGES']}`
+                    (`{$this->CNF['FIELD_NEW_LOT']}`, `{$this->CNF['FIELD_NEW_JOT']}`, `{$this->CNF['FIELD_NEW_UNREAD']}`, `{$this->CNF['FIELD_NEW_PROFILE']}`)
+                VALUES " . implode(',', $aValues) . "
+                ON DUPLICATE KEY UPDATE
+                    `{$this->CNF['FIELD_NEW_UNREAD']}` = `{$this->CNF['FIELD_NEW_UNREAD']}` + 1,
+                    `{$this->CNF['FIELD_NEW_JOT']}` = IF(
+                        `{$this->CNF['FIELD_NEW_JOT']}` = 0 OR `{$this->CNF['FIELD_NEW_JOT']}` > VALUES(`{$this->CNF['FIELD_NEW_JOT']}`),
+                        VALUES(`{$this->CNF['FIELD_NEW_JOT']}`),
+                        `{$this->CNF['FIELD_NEW_JOT']}`
+                    )
+            ");
+        }
+
+        return $bExecuted;
+    }
+
+    function markAsNewJot($mixedProfile, $iLotId, $iJotId, $bUpdateLot = true)
+    {
+        if (is_array($mixedProfile))
+            $this->addNewJotItems($mixedProfile, $iLotId, $iJotId);
+        elseif ((int) $mixedProfile)
+            $this->addNewJotItem($mixedProfile, $iLotId, $iJotId);
+
+        if (!$bUpdateLot)
+            return true;
+
+        return $this->query("UPDATE `{$this->CNF['TABLE_ENTRIES']}` 
+                                            SET `{$this->CNF['FIELD_UPDATED']}` = UNIX_TIMESTAMP() 
+                                            WHERE `{$this->CNF['FIELD_ID']}` = :id", array('id' => $iLotId));
+    }
+
+    function deleteNewJot($iProfileId, $iLotId = 0, $iJotId = 0)
+    {
+        if ($iJotId) {
+            $iLotId = $iLotId ? $iLotId : $this->getLotByJotId($iJotId);
+            $aNewJotInfo = $this->getNewJots($iProfileId, $iLotId);
+            if (empty($aNewJotInfo) || ((int) $aNewJotInfo[$this->CNF['FIELD_NEW_JOT']] > (int) $iJotId))
+                return false;
+
+            $iCount = (int) $this->getOne("SELECT COUNT(*) FROM `{$this->CNF['TABLE_MESSAGES']}`
+									WHERE `{$this->CNF['FIELD_MESSAGE_FK']}`=:lot AND `{$this->CNF['FIELD_MESSAGE_ID']}` > :jot
+									", array('lot' => $iLotId, 'jot' => $iJotId));
+            if ($iCount)
+                return $this->query("REPLACE INTO `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                            SET 
+                                            `{$this->CNF['FIELD_NEW_PROFILE']}`=:profile,
+                                            `{$this->CNF['FIELD_NEW_UNREAD']}`=:number,
+                                            `{$this->CNF['FIELD_NEW_JOT']}`=:jot,
+                                            `{$this->CNF['FIELD_NEW_LOT']}`=:lot                                            
+                                         ", array(
+                    'profile' => $iProfileId,
+                    'lot' => $iLotId,
+                    'number' => $iCount,
+                    'jot' => $iJotId
+                ));
+        }
+
+        return $iLotId ?
+            $this->query("DELETE FROM `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                                        WHERE `{$this->CNF['FIELD_NEW_LOT']}`=:lot AND `{$this->CNF['FIELD_NEW_PROFILE']}`=:profile                                        
+                                                     ", array('profile' => $iProfileId, 'lot' => $iLotId))
+            :
+            $this->query("DELETE FROM `{$this->CNF['TABLE_NEW_MESSAGES']}` 
+                                                        WHERE `{$this->CNF['FIELD_NEW_PROFILE']}`=:profile                                        
+                                                     ", array('profile' => $iProfileId));
+    }
+
+    public function searchMessage($sParam, $iProfileId = 0, $iLotId = 0)
+    {
+        $aResult = array();
+        if (!$sParam)
+            return $aResult;
+
+        $aParams = preg_split('/[\s]/', $sParam, -1, PREG_SPLIT_NO_EMPTY);
+        $sCriteria = implode('%', $aParams);
+        $aWhere = array('criteria' => "%{$sCriteria}%");
+
+        if ($iLotId) {
+            $sWhere = " AND `j`.`{$this->CNF['FIELD_MESSAGE_FK']}`=:id";
+            $aWhere['id'] = (int) $iLotId;
+        }
+
+        if ($iProfileId) {
+            $sWhere = " AND (`l`.`{$this->CNF['FIELD_PARTICIPANTS']}` REGEXP '(^|,){$iProfileId}(,|$)' OR `l`.`{$this->CNF['FIELD_AUTHOR']}`=:profile)";
+            $aWhere['profile'] = (int) $iProfileId;
+        }
+
+        return $this->getPairs("SELECT `j`.`{$this->CNF['FIELD_MESSAGE_ID']}`, `j`.`{$this->CNF['FIELD_MESSAGE_FK']}`
+			 FROM `{$this->CNF['TABLE_MESSAGES']}` as `j`
+			 RIGHT JOIN `{$this->CNF['TABLE_ENTRIES']}` as `l` on `l`.`{$this->CNF['FIELD_ID']}` = `j`.`{$this->CNF['FIELD_MESSAGE_FK']}` 
+             WHERE `j`.`{$this->CNF['FIELD_MESSAGE']}` LIKE :criteria {$sWhere}", $this->CNF['FIELD_MESSAGE_ID'], $this->CNF['FIELD_MESSAGE_FK'], $aWhere);
+    }
+
+    function getMyTalksIdList($iProfileId)
+    {
+        if (!$iProfileId)
+            return [];
+
+        $sField = $this->CNF['FIELD_ID'];
+        $aList = $this->getMyLots($iProfileId);
+        return array_map(function ($aTalk) use ($sField) {
+            return $aTalk[$sField];
+        }, $aList);
+    }
+    /**
+     * Get all member's lots
+     *@param int $iProfileId
+     *@param array $aParams filter params
+     *@param array $aReturn may contain special values for search function
+     *@return array list of lots
+     */
+    public function getMyLots($iProfileId, $aParams = [], &$aReturn = [])
+    {
+        $sOrAddon = $sJoin = $sWhere = '';
+        $aSWhere = array();
+        $aWhere = array('profile' => (int) $iProfileId, 'parts' => '(^|,)' . (int) $iProfileId . '(,|$)');
+
+        // talks groups
+        if (isset($aParams['group']) && (int) $aParams['group']) {
+            $aSWhere[] = " `g`.`{$this->CNF['FMGL_GROUP_ID']}`=:group_id ";
+            $aWhere['group_id'] = (int) $aParams['group'];
+
+            $sOrAddon = " OR `l`.`{$this->CNF['FIELD_TYPE']}` = " . BX_IM_TYPE_PUBLIC;
+        }
+
+        //if threads
+        if (isset($aParams['threads']))
+            $aSWhere[] = " `l`.`{$this->CNF['FIELD_PARENT_JOT']}` !=0 ";
+
+        // Search criteria: the caller's (API) or the ones selected in the module settings.
+        $aCriteria = isset($aParams['search_criteria']) && is_array($aParams['search_criteria']) ? $aParams['search_criteria'] : null;
+        $fIsCriteria = function($sCriteria) use ($aCriteria) {
+            return $aCriteria !== null ? in_array($sCriteria, $aCriteria) : $this->_oConfig->isSearchCriteria($sCriteria);
+        };
+
+        $sParam = isset($aParams['term']) && $aParams['term'] ? $aParams['term'] : '';
+        if ($sParam) {
+            $aParamWhere = [];
+            if ($fIsCriteria(BX_SEARCH_CRITERIA_TITLES)) {
+                $aParamWhere[] = "`l`.`{$this->CNF['FIELD_TITLE']}` LIKE :title";
+                $aWhere['title'] = "%{$sParam}%";
+            }
+
+            if ($fIsCriteria(BX_SEARCH_CRITERIA_PARTS)) {
+                $aProfiles = BxDolService::call('system', 'profiles_search', array($sParam), 'TemplServiceProfiles');
+                if (!empty($aProfiles)) {
+                    $aRegexp = array();
+                    foreach ($aProfiles as &$aProfile) {
+                        // Site: 'value' holds the profile id; API: profiles come as unit data with 'id'.
+                        $iPartId = (int)(isset($aProfile['value']) ? $aProfile['value'] : (isset($aProfile['id']) ? $aProfile['id'] : 0));
+                        if ($iPartId) {
+                            $aRegexp[] = "(^|,){$iPartId}(,|$)";
+                        }
+                    }
+
+                    if (!empty($aRegexp)) {
+                        $aWhere['part_search'] = implode('|', $aRegexp);
+                        $aParamWhere[] = "`{$this->CNF['FIELD_PARTICIPANTS']}` REGEXP :part_search";
+                    }
+                }
+            }
+
+            if ($fIsCriteria(BX_SEARCH_CRITERIA_CONTENT)) {
+                $aSelectedLots = $this->searchMessage($sParam, $iProfileId);
+                if (!empty($aSelectedLots)) {
+                    $aParamWhere[] = "`l`.`{$this->CNF['FIELD_ID']}` IN (" . implode(',', array_unique($aSelectedLots)) . ")";
+                    $aReturn['jots_list'] = $aSelectedLots;
+                }
+            }
+
+            if (!empty($aParamWhere))
+                $aSWhere[] = '(' . implode(' OR ', $aParamWhere) . ')';
+            else
+                return false;
+        }
+
+
+        if (isset($aParams['type']) && $aParams['type']) {
+            if (is_numeric($aParams['type'])) {
+                $aSWhere[] = " `l`.`{$this->CNF['FIELD_TYPE']}` = :type AND `l`.`{$this->CNF['FIELD_PARENT_JOT']}` = 0 ";
+                $aWhere['type'] = (int) $aParams['type'];
+            } else if (is_array($aParams['type'])) {
+                $aSWhere[] = " `l`.`{$this->CNF['FIELD_TYPE']}` IN (" . implode(',', $aParams['type']) . ")  AND `l`.`{$this->CNF['FIELD_PARENT_JOT']}` = 0 ";
+            }
+        } else {
+            $sJoin .= "LEFT JOIN `{$this->CNF['TABLE_MASS_TRACKER']}` as `mt` ON `mt`.`{$this->CNF['FIELD_MASS_CONVO_ID']}` = `l`.`{$this->CNF['FIELD_ID']}` AND `mt`.`{$this->CNF['FIELD_MASS_USER_ID']}`=:profile";
+            $sOrAddon = " OR `mt`.`{$this->CNF['FIELD_MASS_USER_ID']}` IS NOT NULL";
+        }
+
+        $bStar = isset($aParams['star']) && $aParams['star'] === true;
+        if ($bStar) {
+            $sJoin .= " INNER JOIN `{$this->CNF['TABLE_USERS_INFO']}` as `u` ON `u`.`{$this->CNF['FIELD_INFO_LOT_ID']}` = `l`.`{$this->CNF['FIELD_ID']}` AND `u`.`{$this->CNF['FIELD_INFO_USER_ID']}`=:profile";
+            $aSWhere[] = "`u`.`{$this->CNF['FIELD_INFO_STAR']}` = 1";
+        }
+
+        if (isset($aParams['visibility'])) {
+            if ($aParams['visibility'] !== BX_MSG_VISIBILITY_ALL) {
+                $aWhere['visibility'] = $aParams['visibility'];
+                $aSWhere[] = " `l`.`{$this->CNF['FIELD_VISIBILITY']}` = :visibility";
+            }
+        } else {
+            $aSWhere[] = " `l`.`{$this->CNF['FIELD_VISIBILITY']}` = :visibility";
+            $aWhere['visibility'] = BX_MSG_VISIBILITY_VISIBLE;
+        }
+
+        $sFields = $sHaving = '';
+        $iLastLot = isset($aParams['last_lot']) ? (int) $aParams['last_lot'] : 0;
+        if ($iLastLot) {
+            $aWhere['last_lot'] = $iLastLot;
+            $aSWhere[] = "`l`.`{$this->CNF['FIELD_ID']}` < :last_lot";
+        }
+
+        $iLot = isset($aParams['lot']) && (int) $aParams['lot'] ? (int) $aParams['lot'] : 0;
+        if ($iLot) {
+            $aWhere['lot'] = $iLot;
+            $aSWhere[] = "`l`.`{$this->CNF['FIELD_ID']}` = :lot";
+        }
+
+        if (!empty($aSWhere))
+            $sWhere = ' AND ' . implode(' AND ', $aSWhere);
+
+        $aWhere['start'] = isset($aParams['start']) ? (int) $aParams['start'] : 0;
+        $aWhere['per_page'] = isset($aParams['per_page']) ? (int) $aParams['per_page'] : (int) $this->CNF['MAX_LOTS_NUMBER'];
+        $sLimit = "LIMIT :start, :per_page";
+
+        $bShowLeft = isset($aParams['left']);
+        $sLeft = "";
+        if ($bShowLeft)
+            $sLeft = "SQL_CALC_FOUND_ROWS";
+
+        $aResult = $this->getAll("SELECT 
+                                            {$sLeft}
+                                            {$sFields}   
+                                            `l`.*,
+                                             IF (`g`.`{$this->CNF['FMGL_GROUP_ID']}` IS NOT NULL, `g`.`{$this->CNF['FMGL_GROUP_ID']}`, 0) as `{$this->CNF['FMGL_GROUP_ID']}`,
+                                             IF (`l`.`{$this->CNF['FIELD_UPDATED']}` = 0, `l`.`{$this->CNF['FIELD_ADDED']}`, `l`.`{$this->CNF['FIELD_UPDATED']}`) as `order` 
+                                             FROM `{$this->CNF['TABLE_ENTRIES']}` as `l`
+                                             LEFT JOIN `{$this->CNF['TABLE_GROUPS_LOTS']}` as `g` ON `g`.`{$this->CNF['FMGL_LOT_ID']}` = `l`.`{$this->CNF['FIELD_ID']}`
+                                             {$sJoin}                                             
+                                             WHERE (`l`.`{$this->CNF['FIELD_PARTICIPANTS']}` REGEXP :parts OR `l`.`{$this->CNF['FIELD_AUTHOR']}`=:profile {$sOrAddon}) {$sWhere}
+                                             {$sHaving}
+                                             ORDER BY `order` DESC
+                                         {$sLimit}", $aWhere);
+
+        if ($bShowLeft)
+            $aReturn['left'] = (int) $this->getOne("SELECT FOUND_ROWS()") - $aWhere['start'];
+
+        return $aResult;
+    }
+
+    /**
+     * Check if the title of the lot type must contain link
+     *@param int $iType
+     *@return boolean
+     */
+    public function isLinkedTitle($iType)
+    {
+        $sQuery = $this->prepare("SELECT `{$this->CNF['FIELD_TYPE_LINKED']}` FROM `{$this->CNF['TABLE_TYPES']}` WHERE `{$this->CNF['FIELD_TYPE_ID']}` = ? LIMIT 1", $iType);
+        return (int) $this->getOne($sQuery) == 1;
+    }
+
+    /**
+     * Get time when member was online  
+     *@param int  $iProfileId
+     *@return boolean
+     */
+    public function lastOnline($iProfileId)
+    {
+        $sSql = $this->prepare("SELECT 
+				IF (`ts`.`date` != '', `ts`.`date`, `ta`.`logged`) as `logged` 
+			FROM `sys_profiles` AS `tp` 
+			INNER JOIN `sys_accounts` AS `ta` ON `tp`.`account_id`=`ta`.`id` 
+			LEFT JOIN `sys_sessions` AS `ts` ON `tp`.`account_id`=`ts`.`user_id` 
+			WHERE 
+				`tp`.`id` = ? AND 
+				`ta`.`profile_id`=`tp`.`id`
+			LIMIT 1", $iProfileId);
+
+        return $this->getOne($sSql);
+    }
+
+    /**
+     * Delete all profiles info from lots and jots
+     *@param int  $iProfileId
+     *@param bool $bTalks if true to remove the talk where removed user participates
+     *@return int affected rows
+     */
+    public function deleteProfileInfo($iProfileId, $bTalks = false)
+    {
+        $bResult = true;
+
+        $aWhere['profile'] = (int) $iProfileId;
+
+        $bResult &= $this->query("DELETE
+			FROM `{$this->CNF['TABLE_ENTRIES']}`
+			WHERE `{$this->CNF['FIELD_AUTHOR']}`=:profile", $aWhere);
+
+        $aJots = $this->getAll("SELECT *
+			FROM `{$this->CNF['TABLE_MESSAGES']}` 
+			WHERE `{$this->CNF['FIELD_MESSAGE_AUTHOR']}`=:profile", $aWhere);
+
+        foreach ($aJots as &$aJot)
+            $this->clearJotsConnections($aJot[$this->CNF['FIELD_MESSAGE_ID']], $aJot[$this->CNF['FIELD_MESSAGE_FK']]);
+
+        $bResult &= $this->query("DELETE
+			FROM `{$this->CNF['TABLE_MESSAGES']}` 
+			WHERE `{$this->CNF['FIELD_MESSAGE_AUTHOR']}`=:profile", $aWhere);
+
+        $this->query("DELETE FROM `{$this->CNF['TABLE_MASS_TRACKER']}` WHERE `{$this->CNF['FIELD_MASS_USER_ID']}` =:profile_id", ['profile_id' => $iProfileId]);
+
+        if ($bTalks)
+            $bResult &= (bool) $this->query("DELETE 
+                                           FROM `{$this->CNF['TABLE_ENTRIES']}`
+                                           WHERE FIND_IN_SET(:profile, `{$this->CNF['FIELD_PARTICIPANTS']}`) AND `type`=:type", array_merge($aWhere, ['type' => BX_IM_TYPE_PRIVATE]));
+
+        $aLots = $this->getAll("SELECT * 
+			FROM `{$this->CNF['TABLE_ENTRIES']}` 
+			WHERE FIND_IN_SET(:profile, `{$this->CNF['FIELD_PARTICIPANTS']}`)", $aWhere);
+
+        if (empty($aLots))
+            return $bResult;
+
+        foreach ($aLots as &$aLot) {
+            $bResult &= $this->removeParticipant($aLot[$this->CNF['FIELD_ID']], $iProfileId);
+        }
+
+
+        return $bResult;
+    }
+
+    /**
+     * Add attachment to the jot
+     *@param int $iJotId jot id
+     *@param text mixedContent attachment content
+     *@param int sType attachment type
+     *@return int affected rows
+     */
+
+    public function addAttachment($iJotId, $aAttachments, $mixedType = false)
+    {
+        $iJotId = (int) $iJotId;
+        if (!$iJotId || !($aJotInfo = $this->getJotById($iJotId)) || empty($aAttachments))
+            return false;
+
+        if ($mixedType !== false) {
+            if (empty($aJotInfo[$this->CNF['FIELD_MESSAGE_AT']]))
+                $aAttachments = array($mixedType => $aAttachments);
+            else
+                $aAttachments = @unserialize($aJotInfo[$this->CNF['FIELD_MESSAGE_AT']]) + array($mixedType => $aAttachments);
+        }
+
+        $sQuery = $this->prepare("UPDATE `{$this->CNF['TABLE_MESSAGES']}` 
+												SET  
+													 `{$this->CNF['FIELD_MESSAGE_AT']}` = ?
+												WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = ?", @serialize($aAttachments), $iJotId);
+
+        return $this->query($sQuery);
+    }
+
+    /**
+     * Add attachment to the jot
+     *@param int  $iJotId jot id
+     *@param int  $iProfileId profile id
+     *@param string $sMessage
+     *@return int affected rows
+     */
+    public function editJot($iJotId, $iProfileId, $sMessage)
+    {
+        $iJotId = (int) $iJotId;
+        $aJotInfo = array();
+
+        if (!$iJotId || !($aJotInfo = $this->getJotById($iJotId)) || !$sMessage)
+            return false;
+
+        $sWhere = '';
+        if ($aJotInfo[$this->CNF['FIELD_MESSAGE_AT_TYPE']] == BX_ATT_TYPE_REPOST)
+            $sWhere = ",`{$this->CNF['FIELD_MESSAGE_AT_TYPE']}` = '', `{$this->CNF['FIELD_MESSAGE_AT']}` = ''";
+
+        $sMessage = clear_xss($sMessage);
+        $sQuery = $this->prepare("UPDATE `{$this->CNF['TABLE_MESSAGES']}` 
+												SET  `{$this->CNF['FIELD_MESSAGE']}` = ?,
+													 `{$this->CNF['FIELD_MESSAGE_LAST_EDIT']}` = UNIX_TIMESTAMP(),
+													 `{$this->CNF['FIELD_MESSAGE_EDIT_BY']}` = ?
+													 {$sWhere}
+												WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = ?", $sMessage, $iProfileId, $iJotId);
+
+        return $this->query($sQuery);
+    }
+
+    /**
+     * Check if the Jot already has attachment
+     *@param int  $iJotId jot id
+     *@param string $sType attachment type
+     *@return int original attachment Id
+     */
+    public function hasAttachment($iJotId, $sType = BX_ATT_TYPE_REPOST)
+    {
+        $aValues = $this->getRow("SELECT
+									`{$this->CNF['FIELD_MESSAGE_AT']}`, 
+                                    `{$this->CNF['FIELD_MESSAGE_AT_TYPE']}`
+									FROM `{$this->CNF['TABLE_MESSAGES']}` 
+									WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = :id", array('id' => $iJotId));
+
+        if (!isset($aValues[$this->CNF['FIELD_MESSAGE_AT']]))
+            return false;
+
+        if ($aValues[$this->CNF['FIELD_MESSAGE_AT_TYPE']]) {
+            if ($aValues[$this->CNF['FIELD_MESSAGE_AT_TYPE']] == $sType && (int) $aValues[$this->CNF['FIELD_MESSAGE_AT']] && $aValues[$this->CNF['FIELD_MESSAGE_AT']] !== $iJotId)
+                return (int) $aValues[$this->CNF['FIELD_MESSAGE_AT']];
+
+            return false;
+        }
+
+        $mixedResult = @unserialize($aValues[$this->CNF['FIELD_MESSAGE_AT']]);
+        if (is_array($mixedResult)) {
+            if (isset($mixedResult[$sType]) && (int) $mixedResult[$sType] && (int) $mixedResult[$sType] != $iJotId)
+                return (int) $mixedResult[$sType];
+        }
+
+        return false;
+    }
+
+    public function updateFiles($iJotId, $mixedValues)
+    {
+        $sQuery = $this->prepare("UPDATE `{$this->CNF['OBJECT_STORAGE']}` SET " . $this->arrayToSQL($mixedValues) . " WHERE `{$this->CNF['FIELD_ST_ID']}` = ?", $iJotId);
+        return $this->query($sQuery);
+    }
+
+    public function getJotFiles($iJot, $bCount = false, $iProfileId = 0)
+    {
+        $sProfileWhere = '';
+        if ((int) $iProfileId)
+            $sProfileWhere = "AND `t`.`{$this->CNF['FJMT_USER_ID']}`=" . (int) $iProfileId;
+
+        return !$bCount ?
+            $this->getAll("SELECT `f`.*, IF (`t`.`{$this->CNF['FJMT_COLLAPSED']}` IS NULL, 0, 1) as `{$this->CNF['FJMT_COLLAPSED']}` 
+                                        FROM `{$this->CNF['OBJECT_STORAGE']}` as `f` 
+                                        LEFT JOIN `{$this->CNF['TABLE_JOTS_MEDIA_TRACKER']}` as `t` ON `f`.`{$this->CNF['FIELD_ST_ID']}`=`t`.`{$this->CNF['FJMT_FILE_ID']}` {$sProfileWhere} 
+                                        WHERE `{$this->CNF['FIELD_ST_JOT']}` = :id", array('id' => $iJot)) :
+            $this->getOne("SELECT COUNT(*) FROM `{$this->CNF['OBJECT_STORAGE']}` WHERE `{$this->CNF['FIELD_ST_JOT']}` = :id", array('id' => $iJot));
+    }
+
+    private function removeFilesByJotId($iJotId)
+    {
+        $aFiles = $this->getJotFiles($iJotId);
+        if (empty($aFiles))
+            return false;
+
+        $oStorage = BxDolStorage::getObjectInstance($this->CNF['OBJECT_STORAGE']);
+        $bResult = true;
+        foreach ($aFiles as &$aFile) {
+            $bResult &= $oStorage->deleteFile($aFile[$this->CNF['FIELD_ST_ID']], $aFile[$this->CNF['FIELD_ST_AUTHOR']]);
+            $this->removeMediaTracker($aFile[$this->CNF['FIELD_ST_ID']]);
+        }
+
+        return $bResult;
+    }
+
+    public function removeFileFromJot($iJotId, $iFileId)
+    {
+        $aJotInfo = $this->getJotById($iJotId);
+        $aAttachment = $aFiles = [];
+        if ($aJotInfo[$this->CNF['FIELD_MESSAGE_AT_TYPE']] === BX_ATT_TYPE_FILES) {
+            $aFiles = explode(',', $aJotInfo[$this->CNF['FIELD_MESSAGE_AT']]);
+        } else
+            if (!$aJotInfo[$this->CNF['FIELD_MESSAGE_AT_TYPE']] && isset($aJotInfo[$this->CNF['FIELD_MESSAGE_AT']])) {
+                $aAttachment = @unserialize($aJotInfo[$this->CNF['FIELD_MESSAGE_AT']]);
+                if (isset($aAttachment[BX_ATT_TYPE_FILES])) {
+                    $mixedFiles = $aAttachment[BX_ATT_TYPE_FILES];
+                    if (is_string($mixedFiles))
+                        $aFiles = explode(',', $mixedFiles);
+                    else if (is_array($mixedFiles))
+                        $aFiles = $mixedFiles;
+                } else
+                    return false;
+            }
+
+        $oStorage = BxDolStorage::getObjectInstance($this->CNF['OBJECT_STORAGE']);
+        $aFile = $oStorage->getFile($iFileId);
+        $sFileName = $aFile[$this->CNF['FIELD_ST_NAME']];
+
+        if (!empty($aFiles))
+            $aFiles = array_filter($aFiles, function ($sValue) use ($sFileName) {
+                return $sValue !== $sFileName;
+            });
+        else
+            return false;
+
+        $aAttachment[BX_ATT_TYPE_FILES] = $aFiles;
+        $sQuery = $this->prepare("UPDATE `{$this->CNF['TABLE_MESSAGES']}` 
+												SET  
+													 `{$this->CNF['FIELD_MESSAGE_AT_TYPE']}` = '',
+													 `{$this->CNF['FIELD_MESSAGE_AT']}` = ?
+												WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = ?", @serialize($aAttachment), $iJotId);
+
+        return $this->query($sQuery);
+    }
+
+    public function isFileVendor($iFileId, $iProfileId)
+    {
+        $oStorage = BxDolStorage::getObjectInstance($this->CNF['OBJECT_STORAGE']);
+        $aFile = $oStorage->getFile($iFileId);
+        return $aFile[$this->CNF['FIELD_ST_AUTHOR']] == $iProfileId;
+    }
+
+    public function isEmptyJotMessage($iJotId)
+    {
+        $aJotInfo = $this->getJotById($iJotId);
+
+        return empty($aJotInfo) || !$aJotInfo['FIELD_MESSAGE'];
+    }
+
+    public function getFirstUnreadJot($iProfileId, $iLotId)
+    {
+        if (!$iLotId)
+            return 0;
+
+        $aUnreadJots = $this->getNewJots($iProfileId, $iLotId);
+        return !empty($aUnreadJots) ? (int) $aUnreadJots[$this->CNF['FIELD_NEW_JOT']] : 0;
+    }
+
+    /**
+     * @param int $iObjectId item's object ID
+     * @param string $sType module's name from sys_modules
+     * @param int $iProfileId comments' author
+     * @param int $iStart get comments from position
+     * @param int $iPerPage number of the comments to get at once
+     * @return array list of the comments
+     *
+     */
+    public function getLiveComments($iObjectId, $sType, $iProfileId, $iStart = 0, $iPerPage = 0)
+    {
+        $aWhere = array('object' => $iObjectId, 'type' => $sType);
+        $sLimit = $sAuthor = '';
+        if ((int) $iProfileId) {
+            $aWhere['author'] = (int) $iProfileId;
+            $sAuthor = "AND `{$this->CNF['FIELD_LCMTS_AUTHOR']}`=:author";
+        }
+
+        if ($iPerPage) {
+            $aWhere['start'] = (int) $iStart;
+            $aWhere['per_page'] = (int) $iPerPage;
+            $sLimit = "LIMIT :start, :per_page";
+        }
+
+        $aComments = $this->getAll(
+            "SELECT 
+                                                  SQL_CALC_FOUND_ROWS
+                                                  `{$this->CNF['FIELD_LCMTS_ID']}`,
+                                                  `{$this->CNF['FIELD_LCMTS_TEXT']}`,
+                                                  `{$this->CNF['FIELD_LCMTS_AUTHOR']}`,
+                                                  `{$this->CNF['FIELD_LCMTS_DATE']}`,
+                                                  `{$this->CNF['FIELD_LCMTS_OBJECT_ID']}`
+                                                 FROM `{$this->CNF['TABLE_LIVE_COMMENTS']}` as `c`  
+                                                 LEFT JOIN `{$this->CNF['TABLE_CMTS_OBJECTS']}` as `s` ON `s`.`{$this->CNF['FIELD_COBJ_ID']}` = `c`.`{$this->CNF['FIELD_LCMTS_SYS_ID']}`                                                   
+                                                 WHERE `s`.`{$this->CNF['FIELD_COBJ_MODULE']}` = :type AND `{$this->CNF['FIELD_LCMTS_OBJECT_ID']}` = :object {$sAuthor}
+                                                 ORDER BY `{$this->CNF['FIELD_LCMTS_ID']}` DESC
+                                                 {$sLimit}",
+            $aWhere
+        );
+        return array('result' => $aComments, 'total' => (int) $this->getOne("SELECT FOUND_ROWS()"));
+    }
+
+    /**
+     * @param string $sText message body
+     * @param int $iObjectId item's object ID
+     * @param string $sName module's name from sys_modules
+     * @param int $iProfileId comments' author
+     * @param int $iDate comments' time
+     * @return mixed last added comments id or false
+     *
+     */
+
+    public function addLiveComment($sText, $iObjectId, $sName, $iProfileId, $iDate = 0)
+    {
+        $iSystem = $this->getOne("SELECT `{$this->CNF['FIELD_COBJ_ID']}` 
+                                            FROM `{$this->CNF['TABLE_CMTS_OBJECTS']}`
+                                            WHERE `{$this->CNF['FIELD_COBJ_MODULE']}`=:name LIMIT 1", array('name' => $sName));
+        if (!$iSystem)
+            return false;
+
+        $sQuery = $this->prepare("REPLACE INTO `{$this->CNF['TABLE_LIVE_COMMENTS']}`
+                                   SET 
+                                         `{$this->CNF['FIELD_LCMTS_TEXT']}`=?,
+                                         `{$this->CNF['FIELD_LCMTS_AUTHOR']}`=?,
+                                         `{$this->CNF['FIELD_LCMTS_DATE']}`=?,                                         
+                                         `{$this->CNF['FIELD_LCMTS_OBJECT_ID']}`=?,
+                                         `{$this->CNF['FIELD_LCMTS_SYS_ID']}`=?
+                                   ", $sText, $iProfileId, $iDate ? $iDate : time(), $iObjectId, $iSystem);
+
+        return $this->query($sQuery) ? $this->lastId() : false;
+    }
+
+    public function removeLiveComment($iObjectId, $iProfileId)
+    {
+        return $this->query("DELETE FROM `{$this->CNF['TABLE_LIVE_COMMENTS']}` 
+                              WHERE `{$this->CNF['FIELD_LCMTS_AUTHOR']}`=:author AND `{$this->CNF['FIELD_LCMTS_ID']}`=:object_id", array('author' => $iProfileId, 'object_id' => $iObjectId));
+    }
+
+    public function getLotFiles($iLotId, $iStart = 0, $iPerPage = 0)
+    {
+        $aWhere = array('id' => $iLotId);
+
+        $sLimit = '';
+        if ($iPerPage) {
+            $aWhere['start'] = (int) $iStart;
+            $aWhere['per_page'] = (int) $iPerPage;
+            $sLimit = "LIMIT :start, :per_page";
+        }
+
+        return $this->getAll("SELECT `s`.* 
+                                         FROM `{$this->CNF['OBJECT_STORAGE']}` as `s`
+                                         LEFT JOIN `{$this->CNF['TABLE_MESSAGES']}` as `j` ON `s`.`{$this->CNF['FIELD_ST_JOT']}` = `j`.`{$this->CNF['FIELD_MESSAGE_ID']}`
+                                         LEFT JOIN `{$this->CNF['TABLE_ENTRIES']}` as `l` ON `l`.`{$this->CNF['FIELD_ID']}` = `j`.`{$this->CNF['FIELD_MESSAGE_FK']}` 
+                                         WHERE `l`.`{$this->CNF['FIELD_ID']}` = :id 
+                                         ORDER BY `s`.`{$this->CNF['FIELD_ST_ADDED']}` DESC
+                                         {$sLimit}
+                                         ", $aWhere);
+    }
+
+    public function getLotFilesCount($iLot)
+    {
+        return (int) $this->getOne("SELECT COUNT(*)
+                                         FROM `{$this->CNF['OBJECT_STORAGE']}` as `s`
+                                         LEFT JOIN `{$this->CNF['TABLE_MESSAGES']}` as `j` ON `s`.`{$this->CNF['FIELD_ST_JOT']}` = `j`.`{$this->CNF['FIELD_MESSAGE_ID']}`
+                                         LEFT JOIN `{$this->CNF['TABLE_ENTRIES']}` as `l` ON `l`.`{$this->CNF['FIELD_ID']}` = `j`.`{$this->CNF['FIELD_MESSAGE_FK']}` 
+                                         WHERE `l`.`{$this->CNF['FIELD_ID']}` = :id 
+                                         ORDER BY `s`.`{$this->CNF['FIELD_ST_ADDED']}` DESC
+                                         ", array('id' => $iLot));
+    }
+
+    public function getSentNtfsNumber($iProfileId, $iLotId)
+    {
+        $iInterval = (int) $this->CNF['PARAM_NTFS_INTERVAL'];
+
+        return $this->getOne("SELECT COUNT(*)
+                                                FROM `bx_notifications_events`
+                                                WHERE `action`='got_jot_ntfs' 
+                                                AND `type`=:type 
+                                                AND `object_owner_id`=:profile 
+                                                AND `object_id`=:lot_id 
+                                                AND `date` > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL :interval HOUR))",
+            array(
+                'type' => $this->_oConfig->getName(),
+                'profile' => $iProfileId,
+                'lot_id' => $iLotId,
+                'interval' => $iInterval
+            )
+        );
+    }
+
+    /**
+     * @param int $iJotId id of the message
+     * @param int $iProfileId viewer id
+     * @param int $iJotAuthor message's author
+     * @param int $iLotAuthorId author id
+     * @return bool
+     */
+    public function isAllowedToDeleteJot($iJotId, $iProfileId = 0, $iJotAuthor = 0, $iLotAuthorId = 0)
+    {
+        if (!$iJotId)
+            return true;
+
+        if (!($aJot = $this->getJotById($iJotId)))
+            return false;
+
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        $mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_DELETE_MESSAGES, $iProfileId);
+        if ($mixedResult === true)
+            return true;
+
+        if (!$iJotAuthor)
+            $iJotAuthor = $aJot[$this->CNF['FIELD_MESSAGE_AUTHOR']];
+
+        if (!$iLotAuthorId) {
+            $iLotId = $aJot[$this->CNF['FIELD_MESSAGE_FK']];
+            $bIsLotAuthor = $this->isAuthor($iLotId, $iProfileId);
+        } else
+            $bIsLotAuthor = $iLotAuthorId == $iProfileId;
+
+        return ($this->CNF['ALLOW_TO_REMOVE_MESSAGE'] && $iJotAuthor == $iProfileId) || ($this->CNF['ALLOW_TO_MODERATE_MESSAGE_FOR_AUTHORS'] && $bIsLotAuthor);
+    }
+
+
+    public function isAllowedToEditJot($iJotId, $iProfileId)
+    {
+        if (!$iJotId || !($aJot = $this->getJotById($iJotId)))
+            return false;
+
+        $mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_EDIT_MESSAGES, $iProfileId);
+        if ($mixedResult === true)
+            return true;
+
+        $iJotAuthor = $aJot[$this->CNF['FIELD_MESSAGE_AUTHOR']];
+
+        $iLotId = $aJot[$this->CNF['FIELD_MESSAGE_FK']];
+        $bIsLotAuthor = $this->isAuthor($iLotId, $iProfileId);
+
+        return ($this->CNF['ALLOW_TO_REMOVE_MESSAGE'] && +$iJotAuthor === +$iProfileId) || ($this->CNF['ALLOW_TO_MODERATE_MESSAGE_FOR_AUTHORS'] && $bIsLotAuthor);
+    }
+
+    function createJVC($iLotId, $iProfileId)
+    {
+        $aJVC = $this->getJVC($iLotId);
+        $aOpened = array();
+
+        if (empty($aJVC)) {
+            $aLotInfo = $this->getLotInfoById($iLotId);
+            $sRoom = $this->_oConfig->getRoomId($aLotInfo[$this->CNF['FIELD_ID']], $aLotInfo[$this->CNF['FIELD_AUTHOR']]);
+            $this->query(
+                "INSERT INTO `{$this->CNF['TABLE_JVC']}` 
+                SET 
+                    `{$this->CNF['FJVC_ROOM']}`=:room,
+                    `{$this->CNF['FJVC_LOT_ID']}`=:lot_id,
+                    `{$this->CNF['FJVC_NUMBER']}`=1
+                ",
+                array(
+                    'room' => $sRoom,
+                    'lot_id' => $iLotId
+                )
+            );
+
+            $iJVCId = $this->lastId();
+        } else {
+            $iJVCId = $aJVC[$this->CNF['FJVC_ID']];
+            $aOpened = $this->closeAllOpenedConversations($iJVCId);
+            $sRoom = $aJVC[$this->CNF['FJVC_ROOM']];
+            $this->query("UPDATE `{$this->CNF['TABLE_JVC']}` 
+                        SET `{$this->CNF['FJVC_NUMBER']}`= 1
+                        WHERE `{$this->CNF['FJVC_LOT_ID']}`= :lot_id    
+                        ", array('lot_id' => $iLotId));
+        }
+
+        if ($iJVCId && ($iJVCItemId = $this->addJVCItem($iJVCId, $iProfileId))) {
+            $this->updateJVC($iLotId, $this->CNF['FJVC_ACTIVE'], $iJVCItemId);
+            $iJotId = $this->addJot($iLotId, '', $iProfileId);
+            $this->updateJot($iJotId, $this->CNF['FIELD_MESSAGE_VIDEOC'], $iJVCItemId);
+        }
+
+        return array('jitsi_id' => $iJVCId, 'jot_id' => $iJotId, $this->CNF['FJVC_ROOM'] => $sRoom, 'opened' => array_values($aOpened));
+    }
+
+    public function getJotIdByJitsiItem($iJitsiId)
+    {
+        $sQuery = $this->prepare("SELECT `{$this->CNF['FIELD_MESSAGE_ID']}` 
+                                            FROM `{$this->CNF['TABLE_MESSAGES']}` 
+                                            WHERE `{$this->CNF['FIELD_MESSAGE_VIDEOC']}` = ? 
+                                            ORDER BY `{$this->CNF['FIELD_MESSAGE_ID']}` DESC
+                                            LIMIT 1", $iJitsiId);
+        return $this->getOne($sQuery);
+    }
+
+    function addJVCItem($iId, $iProfileId)
+    {
+        $this->query(
+            "INSERT INTO `{$this->CNF['TABLE_JVCT']}` SET 
+                            `{$this->CNF['FJVCT_AUTHOR_ID']}` = :author,
+                            `{$this->CNF['FJVCT_FK']}`= :id,
+                            `{$this->CNF['FJVCT_PART']}`= :part,
+                            `{$this->CNF['FJVCT_JOINED']}`= :part,
+                            `{$this->CNF['FJVCT_START']}` = UNIX_TIMESTAMP()
+                            ",
+            array(
+                'author' => $iProfileId,
+                'id' => $iId,
+                'part' => $iProfileId
+            )
+        );
+
+        return $this->lastId();
+    }
+
+    function closeAllOpenedConversations($iJVCId)
+    {
+        $aAllOpenedChats = $this->getPairs("SELECT  `j`.`{$this->CNF['FIELD_MESSAGE_ID']}`, `v`.`{$this->CNF['FJVCT_ID']}` as `jot_id` 
+                                                            FROM `{$this->CNF['TABLE_JVCT']}` as `v` 
+                                                            LEFT JOIN `{$this->CNF['TABLE_MESSAGES']}` as `j` ON `v`.`{$this->CNF['FJVCT_ID']}` = `j`.`{$this->CNF['FIELD_MESSAGE_VIDEOC']}`                                                                                                  
+                                                            WHERE `{$this->CNF['FJVCT_FK']}`= :id AND `{$this->CNF['FJVCT_END']}`= 0", 'jot_id', $this->CNF['FIELD_MESSAGE_ID'], array('id' => $iJVCId));
+
+        if (empty($aAllOpenedChats))
+            return array();
+
+        $this->query(
+            "UPDATE `{$this->CNF['TABLE_JVCT']}`
+                                  SET
+                                        `{$this->CNF['FJVCT_END']}`= UNIX_TIMESTAMP()                                        
+                                  WHERE `{$this->CNF['FJVCT_FK']}`= :id AND `{$this->CNF['FJVCT_END']}`= 0",
+            array('id' => $iJVCId)
+        );
+
+        return $aAllOpenedChats;
+    }
+
+    function joinToActiveJVC($iLotId, $iProfileId, $bJoin = true)
+    {
+        $aItem = $this->getActiveJVCItem($iLotId);
+        if (empty($aItem))
+            return false;
+
+        $aParticipants = array();
+        if ($aItem[$this->CNF['FJVCT_PART']])
+            $aParticipants = explode(',', $aItem[$this->CNF['FJVCT_PART']]);
+
+        if (!in_array($iProfileId, $aParticipants))
+            $aParticipants[] = $iProfileId;
+
+        $aJoined = $aItem[$this->CNF['FJVCT_JOINED']] ? explode(',', $aItem[$this->CNF['FJVCT_JOINED']]) : array();
+        if (!in_array($iProfileId, $aJoined))
+            $aJoined[] = $iProfileId;
+
+        $this->updateJVCItem($aItem[$this->CNF['FJVCT_ID']], $this->CNF['FJVCT_PART'], implode(',', $aParticipants));
+        if ($bJoin)
+            $this->updateJVCItem($aItem[$this->CNF['FJVCT_ID']], $this->CNF['FJVCT_JOINED'], implode(',', $aJoined));
+
+        $this->updateJVC($iLotId, $this->CNF['FJVC_NUMBER'], count($aParticipants));
+        return true;
+    }
+
+    function updateJVC($iLotId, $sField, $mixedValue)
+    {
+        return $this->query(
+            "UPDATE `{$this->CNF['TABLE_JVC']}` SET 
+                            `{$sField}`=:value                                                       
+                            WHERE `{$this->CNF['FJVC_LOT_ID']}`=:lot_id",
+            array(
+                'lot_id' => $iLotId,
+                'value' => $mixedValue
+            )
+        );
+    }
+
+    function leaveJVC($iLotId, $sField, $mixedValue)
+    {
+        return $this->query(
+            "UPDATE `{$this->CNF['TABLE_JVC']}` SET 
+                            `{$sField}`=:value                                                       
+                            WHERE `{$this->CNF['FJVC_LOT_ID']}`=:lot_id",
+            array(
+                'lot_id' => $iLotId,
+                'value' => $mixedValue
+            )
+        );
+    }
+
+    function updateJVCItem($iId, $sField, $mixedValue)
+    {
+        return $this->query(
+            "UPDATE `{$this->CNF['TABLE_JVCT']}` SET 
+                            `{$sField}`=:value                                                       
+                            WHERE `{$this->CNF['FJVC_ID']}`=:id",
+            array(
+                'id' => $iId,
+                'value' => $mixedValue
+            )
+        );
+    }
+
+    function closeJVC($iLotId)
+    {
+        $aJVC = $this->getJVC($iLotId);
+        if (empty($aJVC) || !(int) $aJVC[$this->CNF['FJVC_ACTIVE']])
+            return false;
+
+        $this->query(
+            "UPDATE `{$this->CNF['TABLE_JVCT']}` 
+                                        SET                                                    
+                                        `{$this->CNF['FJVCT_JOINED']}`=:joined,
+                                        `{$this->CNF['FJVCT_END']}`= UNIX_TIMESTAMP()                                              
+                                        WHERE `{$this->CNF['FJVCT_ID']}`= :id",
+            array(
+                'id' => $aJVC[$this->CNF['FJVC_ACTIVE']],
+                'joined' => '',
+            )
+        );
+
+        $this->updateJVC($iLotId, $this->CNF['FJVC_ACTIVE'], 0);
+        return $this->updateJVC($iLotId, $this->CNF['FJVC_NUMBER'], 0);
+    }
+
+    function stopJVC($iLotId, $iProfileId)
+    {
+        $aJVC = $this->getJVC($iLotId);
+        if (empty($aJVC) || !(int) $aJVC[$this->CNF['FJVC_ACTIVE']])
+            return false;
+
+        $aTrack = $this->getRow(
+            "SELECT * FROM `{$this->CNF['TABLE_JVCT']}`                                                                                           
+                                        WHERE `{$this->CNF['FJVCT_ID']}`= :id",
+            array(
+                'id' => $aJVC[$this->CNF['FJVC_ACTIVE']]
+            )
+        );
+
+        $aJoined = explode(',', $aTrack[$this->CNF['FJVCT_JOINED']]);
+        if (in_array($iProfileId, $aJoined))
+            unset($aJoined[array_search($iProfileId, $aJoined)]);
+        else
+            $this->joinToActiveJVC($iLotId, $iProfileId, false);
+
+        $sEnd = '';
+        $iJoinedNumber = count($aJoined);
+        if (!$iJoinedNumber)
+            $sEnd = ",`{$this->CNF['FJVCT_END']}`= UNIX_TIMESTAMP()";
+
+        $this->query(
+            "UPDATE `{$this->CNF['TABLE_JVCT']}` 
+                                        SET                                                    
+                                        `{$this->CNF['FJVCT_JOINED']}`=:joined
+                                        {$sEnd}                                                
+                                        WHERE `{$this->CNF['FJVCT_ID']}`= :id",
+            array(
+                'id' => $aJVC[$this->CNF['FJVC_ACTIVE']],
+                'joined' => implode(',', $aJoined),
+            )
+        );
+
+        if (!$iJoinedNumber)
+            $this->updateJVC($iLotId, $this->CNF['FJVC_ACTIVE'], 0);
+
+        return $this->updateJVC($iLotId, $this->CNF['FJVC_NUMBER'], $iJoinedNumber);
+    }
+
+    function getJVC($iLotId)
+    {
+        return $this->getRow(
+            "SELECT * FROM `{$this->CNF['TABLE_JVC']}`
+                                     WHERE `{$this->CNF['FJVC_LOT_ID']}`= :lot_id",
+            array('lot_id' => $iLotId)
+        );
+    }
+
+    function getJVCItem($iId)
+    {
+        return $this->getRow(
+            "SELECT * 
+                                    FROM `{$this->CNF['TABLE_JVCT']}` 
+                                    WHERE `{$this->CNF['FJVCT_ID']}`= :id",
+            array('id' => $iId)
+        );
+    }
+
+    function getActiveJVCItem($iLotId, $sFiled = '')
+    {
+        $aJVC = $this->getJVC($iLotId);
+        if (empty($aJVC) || !(int) $aJVC[$this->CNF['FJVC_ACTIVE']])
+            return false;
+
+        $aInfo = $this->getJVCItem($aJVC[$this->CNF['FJVC_ACTIVE']]);
+        if ($sFiled)
+            return isset($aInfo[$sFiled]) ? $aInfo[$sFiled] : false;
+
+        return $aInfo;
+    }
+
+    /*********************** REACT JOT Integration part *****************/
+
+    /**
+     * Returns installed list of modules with comments ability
+     * @param string $sModule
+     * @return array
+     */
+    function getAllCmtsModule($sModule = '')
+    {
+        return $this->getAllWithKey("SELECT 
+                    `c`.`Module` as `module`,
+                    `TriggerTable` as `table`,
+                    `TriggerFieldId` as `id`,
+                    `TriggerFieldAuthor` as `owner`,
+                    `TriggerFieldTitle` as `title`,
+                    `TriggerFieldComments` as `cmts`,
+                    `sm`.*,
+                    `m`.`icon`
+                FROM `sys_objects_cmts` as `c`
+                LEFT JOIN `sys_modules` as `sm` ON `sm`.`name` = `c`.`module`
+                LEFT JOIN `sys_menu_items` as `m` ON `c`.`module` = `m`.`module` AND `m`.`set_name` = 'sys_site'
+                WHERE `c`.`module` != 'bx_timeline' 
+                GROUP BY `c`.`module`
+                ", 'module');
+    }
+
+    function addJotReaction($iJotId, $iProfileId, $aEmoji)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return $this->query("REPLACE INTO `{$CNF['TABLE_JOT_REACTIONS']}` 
+                                        SET 
+                                            `{$CNF['FIELD_REACT_JOT_ID']}` = :jot_id,
+                                            `{$CNF['FIELD_REACT_NATIVE']}` = :native,
+                                            `{$CNF['FIELD_REACT_EMOJI_ID']}` = :emoji_id,
+                                            `{$CNF['FIELD_REACT_PROFILE_ID']}` = :profile_id,
+                                            `{$CNF['FIELD_REACT_ADDED']}` = UNIX_TIMESTAMP()
+                                        ", array(
+            'jot_id' => $iJotId,
+            'native' => $aEmoji['native'],
+            'emoji_id' => $aEmoji['id'],
+            'profile_id' => $iProfileId
+        ));
+    }
+
+    function deleteReaction($iJotId, $iProfileId, $sEmoji)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return $this->query("DELETE FROM `{$CNF['TABLE_JOT_REACTIONS']}` 
+                                        WHERE 
+                                            `{$CNF['FIELD_REACT_JOT_ID']}` = :jot_id AND 
+                                            `{$CNF['FIELD_REACT_PROFILE_ID']}` = :profile_id AND 
+                                            `{$CNF['FIELD_REACT_EMOJI_ID']}` = :emoji_id
+                                        ", array(
+            'jot_id' => $iJotId,
+            'profile_id' => $iProfileId,
+            'emoji_id' => $sEmoji
+        ));
+    }
+
+    function deleteJotReactions($iJotId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return $this->query("DELETE FROM `{$CNF['TABLE_JOT_REACTIONS']}` 
+                                        WHERE 
+                                            `{$CNF['FIELD_REACT_JOT_ID']}` = :jot_id
+                                        ", array(
+            'jot_id' => $iJotId
+        ));
+    }
+
+    function updateReaction($iJotId, $iProfileId, $sEmojiId, $sAction = BX_JOT_REACTION_ADD)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if ($sAction === BX_JOT_REACTION_ADD) {
+            $sNative = $this->getOne("SELECT `{$CNF['FIELD_REACT_NATIVE']}` 
+                                                FROM `{$CNF['TABLE_JOT_REACTIONS']}` 
+                                                WHERE `{$CNF['FIELD_REACT_JOT_ID']}` = :jot_id AND `{$CNF['FIELD_REACT_EMOJI_ID']}`=:emoji LIMIT 1", ['jot_id' => $iJotId, 'emoji' => $sEmojiId]);
+            return $this->addJotReaction($iJotId, $iProfileId, array('native' => $sNative, 'id' => $sEmojiId));
+        }
+
+        return $this->deleteReaction($iJotId, $iProfileId, $sEmojiId);
+    }
+
+    function getJotReactions($iJotId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return $this->getAll("SELECT * FROM `{$CNF['TABLE_JOT_REACTIONS']}` 
+                                        WHERE 
+                                            `{$CNF['FIELD_REACT_JOT_ID']}` = :jot_id
+                                        ORDER BY `{$CNF['FIELD_REACT_ADDED']}`", array('jot_id' => $iJotId));
+    }
+
+    public function findInHistory($iLotId = 0, $sText = '', $iStart = 0, $iLimit = 10, $sOrder = 'DESC', $bSkipDeleted = false)
+    {
+        if (!$sText)
+            return array();
+
+        $aWhere[] = "`j`.`{$this->CNF['FIELD_MESSAGE']}` LIKE :message";
+        $aParams['message'] = "%{$sText}%";
+
+        if ($iLotId) {
+            $aWhere[] = " `l`.`{$this->CNF['FIELD_ID']}`=:id ";
+            $aParams['id'] = $iLotId;
+        }
+
+        if ($bSkipDeleted) {
+            $aWhere[] = " `j`.`{$this->CNF['FIELD_MESSAGE_TRASH']}` = 0 ";
+        }
+
+        $sLimit = '';
+        $sOrder = $sOrder === 'ASC' ? 'ASC' : 'DESC';
+        if ($iLimit) {
+            $aParams['start'] = (int) $iStart;
+            $aParams['limit'] = (int) $iLimit;
+            $sLimit = "LIMIT :start, :limit";
+        }
+
+        if (!empty($aWhere))
+            $sWhere = implode(' AND ', $aWhere);
+
+        return $this->getAll("SELECT 
+			`l`.`{$this->CNF['FIELD_TITLE']}`,
+			`l`.`{$this->CNF['FIELD_TYPE']}`,
+			`l`.`{$this->CNF['FIELD_ADDED']}` as `talk_added`,
+			`l`.`{$this->CNF['FIELD_AUTHOR']}` as `talk_author`,
+			`l`.`{$this->CNF['FIELD_PARTICIPANTS']}`,
+			`j`.`{$this->CNF['FIELD_MESSAGE']}`,
+			`j`.`{$this->CNF['FIELD_MESSAGE_ID']}` as `message_id`,	
+			`j`.`{$this->CNF['FIELD_ADDED']}` as `message_added`,			
+			`j`.`{$this->CNF['FIELD_MESSAGE_AUTHOR']}` as `message_author`			
+			FROM `{$this->CNF['TABLE_ENTRIES']}` as `l`
+			LEFT JOIN `{$this->CNF['TABLE_MESSAGES']}` as `j` ON `l`.`{$this->CNF['FIELD_ID']}` = `j`.`{$this->CNF['FIELD_MESSAGE_FK']}`
+			WHERE {$sWhere}
+			ORDER BY `j`.`{$this->CNF['FIELD_MESSAGE_ID']}` {$sOrder}
+			{$sLimit}", $aParams);
+    }
+
+    public function removeNotifications($iJotId, $iConvoId = 0)
+    {
+        $sModule = $this->_oConfig->getName();
+        if ($iConvoId)
+            return $this->query("DELETE FROM `bx_notifications_events` WHERE `object_id`=:id AND `type`=:name", ['id' => $iConvoId, 'name' => $sModule]);
+
+        return $this->query("DELETE FROM `bx_notifications_events` WHERE `subobject_id`=:id AND `type`=:name", ['id' => $iJotId, 'name' => $sModule]);
+    }
+
+    public function markNotificationAsRead($iRecipientId, $iLotId)
+    {
+        if (!$this->isModuleByName('bx_notifications'))
+            return false;
+
+        $aEvents = $this->getAll("SELECT `e`.`id` 
+                                                FROM `bx_notifications_events` AS `e`
+                                                LEFT JOIN `bx_notifications_queue` AS `q` ON `q`.`event_id` = `e`.`id`                                       
+                                                WHERE `action`='got_jot_ntfs' 
+                                                    AND `e`.`type`=:type                                               
+                                                    AND `e`.`object_owner_id`=:object_owner_id
+                                                    AND `e`.`object_id`=:lot_id",
+            array(
+                'type' => $this->_oConfig->getName(),
+                'lot_id' => $iLotId,
+                'object_owner_id' => $iRecipientId
+            )
+        );
+
+        if (empty($aEvents))
+            return false;
+
+        foreach ($aEvents as &$aEvent) {
+            if (
+                $this->query(
+                    "DELETE FROM `bx_notifications_events` WHERE `id`=:id",
+                    array(
+                        'id' => $aEvent['id']
+                    )
+                )
+            )
+                $this->query("DELETE FROM `bx_notifications_queue` WHERE `profile_id`=:profile_id AND `event_id`=:event_id", array(
+                    'profile_id' => $iRecipientId,
+                    'event_id' => $aEvent['id']
+                ));
+        }
+    }
+
+    function getPublicRoomParticipants($sRoom)
+    {
+        $aRoom = $this->getPublicVideoRoom($sRoom);
+        if (empty($aRoom) || !$aRoom[$this->CNF['FPJVC_PARTS']])
+            return array();
+
+        return explode(',', $aRoom[$this->CNF['FPJVC_PARTS']]);
+    }
+
+    function createPublicVideoRoom($sRoom, $iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $aRoom = $this->getPublicVideoRoom($sRoom);
+
+        if (!empty($aRoom)) {
+            if (!(int) $aRoom[$CNF['FPJVC_STATUS']])
+                return $this->query("UPDATE `{$this->CNF['TABLE_PUBLIC_JVC']}` 
+                                    SET                                         
+                                        `{$this->CNF['FPJVC_CREATED']}`=UNIX_TIMESTAMP(),
+                                        `{$this->CNF['FPJVC_STATUS']}`=1,
+                                        `{$this->CNF['FPJVC_PARTS']}`=:part
+                                    WHERE `{$this->CNF['FPJVC_ROOM']}`=:room                                         
+                                   ", array('room' => $sRoom, 'part' => $iProfileId));
+            else
+                return $this->updatePublicVideoRoom($sRoom, $iProfileId, BX_JOT_PUBLIC_JITSI_JOIN);
+
+        }
+
+        return $this->query("REPLACE INTO `{$this->CNF['TABLE_PUBLIC_JVC']}` 
+                                    SET 
+                                        `{$this->CNF['FPJVC_ROOM']}`=:room,
+                                        `{$this->CNF['FPJVC_CREATED']}`=UNIX_TIMESTAMP(),
+                                        `{$this->CNF['FPJVC_PARTS']}`=:part                                     
+                                   ", array('room' => $sRoom, 'part' => $iProfileId));
+    }
+
+    function getPublicVideoRoom($sRoom)
+    {
+        if (!$sRoom)
+            return false;
+
+        return $this->getRow("SELECT * FROM `{$this->CNF['TABLE_PUBLIC_JVC']}` 
+                                        WHERE `{$this->CNF['FPJVC_ROOM']}`=:room LIMIT 1", array('room' => $sRoom));
+    }
+
+    function updatePublicVideoRoom($sRoom, $iProfileId, $sAction)
+    {
+        $aRoom = $this->getPublicVideoRoom($sRoom);
+        if (empty($aRoom))
+            return false;
+
+        if ($sAction == BX_JOT_PUBLIC_JITSI_CLOSE)
+            return $this->query("UPDATE `{$this->CNF['TABLE_PUBLIC_JVC']}` 
+                                    SET
+                                        `{$this->CNF['FPJVC_STATUS']}`=0
+                                    WHERE `{$this->CNF['FPJVC_ROOM']}`=:room                                         
+                                   ", array('room' => $sRoom));
+
+        $aParticipants = array();
+        if ($aRoom[$this->CNF['FPJVC_PARTS']])
+            $aParticipants = explode(',', $aRoom[$this->CNF['FPJVC_PARTS']]);
+
+        if ($sAction == BX_JOT_PUBLIC_JITSI_JOIN && !in_array($iProfileId, $aParticipants))
+            $aParticipants[] = $iProfileId;
+
+        if ($sAction == BX_JOT_PUBLIC_JITSI_LEAVE)
+            $aParticipants = array_diff($aParticipants, array($iProfileId));
+
+        return $this->query(
+            "UPDATE `{$this->CNF['TABLE_PUBLIC_JVC']}` 
+                                    SET
+                                        `{$this->CNF['FPJVC_PARTS']}`=:parts,
+                                        `{$this->CNF['FPJVC_STATUS']}`=:status
+                                    WHERE `{$this->CNF['FPJVC_ROOM']}`=:room                                         
+                                   ",
+            array(
+                'room' => $sRoom,
+                'parts' => implode(',', $aParticipants),
+                'status' => (int) !empty($aParticipants)
+            )
+        );
+    }
+
+    function saveLotSettings($iLotId, $mixedOptions, $sField = 'actions')
+    {
+        if (!$iLotId || !$sField)
+            return false;
+
+        if (empty($mixedOptions))
+            $mixedOptions = [];
+
+        $mixedData = is_array($mixedOptions) ? @serialize($mixedOptions) : (int) $mixedOptions;
+        $aLotSettings = $this->getLotSettings($iLotId, false);
+        if (empty($aLotSettings)) {
+            return $this->query("INSERT INTO `{$this->CNF['TABLE_LOT_SETTINGS']}` 
+                                           SET 
+                                             `{$sField}` = :data,
+                                             `{$this->CNF['FLS_ID']}` = :id", array('data' => $mixedData, 'id' => $iLotId));
+        }
+
+        return $this->query("UPDATE `{$this->CNF['TABLE_LOT_SETTINGS']}` 
+                                       SET 
+                                         `{$sField}` = :actions
+                                       WHERE `{$this->CNF['FLS_ID']}` = :id", array('actions' => $mixedData, 'id' => $iLotId));
+    }
+
+    function isActionAllowed($iLotId, $sAction = BX_MSG_SETTING_MSG)
+    {
+        $mixedOptions = $this->getLotSettings($iLotId);
+        if ($mixedOptions === false)
+            return true;
+
+        return in_array($sAction, $mixedOptions);
+    }
+
+    function getLotSettings($iLotId, $sField = 'actions')
+    {
+        $mixedOptions = false;
+        if (!$iLotId || !($mixedOptions = $this->getRow("SELECT * FROM `{$this->CNF['TABLE_LOT_SETTINGS']}` WHERE `{$this->CNF['FLS_ID']}` = :id", array('id' => $iLotId))))
+            return false;
+
+        return $sField && isset($mixedOptions[$sField]) ?
+            ($sField !== $this->CNF['FLS_ICON'] ? @unserialize($mixedOptions[$sField]) : (int) $mixedOptions[$sField]) : $mixedOptions;
+    }
+
+    function getLotAttachmentType($sName)
+    {
+        $sService = $this->getOne("SELECT `{$this->CNF['FLAT_SERVICE']}` FROM `{$this->CNF['TABLE_LOT_ATTACHMENTS']}` WHERE `{$this->CNF['FLAT_NAME']}`=:name LIMIT 1", array('name' => $sName));
+        return $sService ? @unserialize($sService) : false;
+    }
+
+    function updateLotAttachmentType(&$aData)
+    {
+        if (!isset($aData['name']) || !isset($aData['service']))
+            return false;
+
+        $sService = is_array($aData['service']) ? @serialize($aData['service']) : $aData['service'];
+
+        return $this->query(
+            "REPLACE INTO `{$this->CNF['TABLE_LOT_ATTACHMENTS']}` 
+                                            SET 
+                                                `{$this->CNF['FLSE_NAME']}`=:name, 
+                                                `{$this->CNF['FLAT_SERVICE']}`=:service",
+            array('name' => $aData['name'], 'service' => $sService)
+        );
+    }
+
+    function getGroup($iGroupId)
+    {
+        return $this->getRow("SELECT * FROM `{$this->CNF['TABLE_GROUPS']}` WHERE `{$this->CNF['FMG_ID']}`=:id LIMIT 1", array('id' => $iGroupId));
+    }
+
+    function addGroup($iProfileId, $aParams)
+    {
+        $aGroup = array();
+        if (isset($aParams[$this->CNF['FMG_ID']]))
+            $aGroup = $this->getGroup($aParams[$this->CNF['FMG_ID']]);
+
+        if (empty($aGroup))
+            return $this->query(
+                "INSERT INTO `{$this->CNF['TABLE_GROUPS']}` 
+                                                 SET `{$this->CNF['FMG_NAME']}` = :name, 
+                                                     `{$this->CNF['FMG_PRIVACY']}` = :privacy,
+                                                     `{$this->CNF['FMG_AUTHOR']}` = :author,
+                                                     `{$this->CNF['FMG_DESC']}` = :desc,
+                                                     `{$this->CNF['FMG_ADDED']}` = UNIX_TIMESTAMP()",
+                array(
+                    'name' => $aParams[$this->CNF['FMG_NAME']],
+                    'author' => $iProfileId,
+                    'desc' => $aParams[$this->CNF['FMG_DESC']],
+                    'privacy' => $aParams[$this->CNF['FMG_PRIVACY']]
+                )
+            );
+
+
+        return $this->query(
+            "UPDATE `{$this->CNF['TABLE_GROUPS']}`
+                                                 SET `{$this->CNF['FMG_NAME']}` = :name, 
+                                                     `{$this->CNF['FMG_PRIVACY']}` = :privacy, 
+                                                     `{$this->CNF['FMG_DESC']}` = :desc
+                                       WHERE  `{$this->CNF['FMG_ID']}`=:id",
+            array('name' => $aParams[$this->CNF['FMG_NAME']], 'desc' => $aParams[$this->CNF['FMG_DESC']], 'privacy' => $aParams[$this->CNF['FMG_PRIVACY']], 'id' => $aParams[$this->CNF['FMG_ID']])
+        );
+    }
+
+    function getGroups($iProfileId = 0)
+    {
+        if ($iProfileId)
+            return $this->getAll("SELECT * FROM `{$this->CNF['TABLE_GROUPS']}` WHERE  `{$this->CNF['FMG_AUTHOR']}`=:profile", array('author' => $iProfileId));
+
+        return $this->getAll("SELECT * FROM `{$this->CNF['TABLE_GROUPS']}` ORDER BY {$this->CNF['FMG_NAME']}", array('author' => $iProfileId));
+    }
+
+    function addLotToGroup($iLotId, $iGroupId)
+    {
+        return $this->query("REPLACE INTO `{$this->CNF['TABLE_GROUPS_LOTS']}` 
+                                               SET `{$this->CNF['FMGL_LOT_ID']}`=:lot_id,
+                                                   `{$this->CNF['FMGL_GROUP_ID']}`=:group_id", array('group_id' => $iGroupId, 'lot_id' => $iLotId));
+    }
+
+    function saveJotItem($iJotId, $iProfileId)
+    {
+        return $this->query("REPLACE INTO `{$this->CNF['TABLE_SAVED_JOTS']}` 
+                                               SET  `{$this->CNF['FSJ_ID']}`=:jot_id,
+                                                    `{$this->CNF['FSJ_PROFILE_ID']}`=:profile_id", array('jot_id' => $iJotId, 'profile_id' => $iProfileId));
+    }
+
+    /**
+     * Talks where the member has saved messages
+     * @param int $iProfileId profile id
+     * @return array lots ids
+     */
+    public function getSavedLotIds($iProfileId)
+    {
+        if (!$iProfileId) {
+            return [];
+        }
+
+        return array_map('intval', $this->getColumn("SELECT DISTINCT `m`.`{$this->CNF['FIELD_MESSAGE_FK']}`
+                                    FROM `{$this->CNF['TABLE_SAVED_JOTS']}` AS `s`
+                                    INNER JOIN `{$this->CNF['TABLE_MESSAGES']}` AS `m` ON `m`.`{$this->CNF['FIELD_MESSAGE_ID']}` = `s`.`{$this->CNF['FSJ_ID']}`
+                                    WHERE `s`.`{$this->CNF['FSJ_PROFILE_ID']}` = :profile_id", ['profile_id' => (int)$iProfileId]));
+    }
+
+    /**
+     * Messages from the list that the member saved
+     * @param int $iProfileId profile id
+     * @param array $aJotIds jots ids
+     * @return array jots ids
+     */
+    public function getSavedJotsByIds($iProfileId, $aJotIds)
+    {
+        $aJotIds = array_filter(array_map('intval', (array)$aJotIds));
+        if (!$iProfileId || empty($aJotIds)) {
+            return [];
+        }
+
+        return $this->getColumn("SELECT `{$this->CNF['FSJ_ID']}` FROM `{$this->CNF['TABLE_SAVED_JOTS']}`
+                                    WHERE `{$this->CNF['FSJ_PROFILE_ID']}` = :profile_id AND `{$this->CNF['FSJ_ID']}` IN (" . implode(',', $aJotIds) . ")", ['profile_id' => (int)$iProfileId]);
+    }
+
+    function isJotSaved($iJotId, $iProfileId)
+    {
+        return $this->getOne(
+            "SELECT COUNT(*) FROM `{$this->CNF['TABLE_SAVED_JOTS']}` 
+                                               WHERE  `{$this->CNF['FSJ_ID']}`=:jot_id AND `{$this->CNF['FSJ_PROFILE_ID']}`=:profile_id",
+            ['jot_id' => $iJotId, 'profile_id' => $iProfileId]
+        ) != 0;
+    }
+
+    function deleteSavedJotItems($iJotId = 0, $iProfileId = 0)
+    {
+        if ($iJotId && $iProfileId)
+            return $this->query(
+                "DELETE FROM `{$this->CNF['TABLE_SAVED_JOTS']}` 
+                                           WHERE `{$this->CNF['FSJ_ID']}`=:jot_id AND `{$this->CNF['FSJ_PROFILE_ID']}`=:profile_id",
+                array('jot_id' => $iJotId, 'profile_id' => $iProfileId)
+            );
+
+        if ($iJotId)
+            $this->query(
+                "DELETE FROM `{$this->CNF['TABLE_SAVED_JOTS']}` 
+                                    WHERE `{$this->CNF['FSJ_ID']}`=:jot_id",
+                array('jot_id' => $iJotId)
+            );
+
+        return $iProfileId ? $this->query(
+            "DELETE FROM `{$this->CNF['TABLE_SAVED_JOTS']}` 
+                                           WHERE `{$this->CNF['FSJ_PROFILE_ID']}`=:profile_id",
+            array('profile_id' => $iProfileId)
+        ) : false;
+    }
+
+    function getSavedJots($iProfileId)
+    {
+        return $this->getAllWithKey("SELECT `m`.* FROM `{$this->CNF['TABLE_SAVED_JOTS']}` as `s`
+                                           LEFT JOIN `{$this->CNF['TABLE_MESSAGES']}` as `m` ON `s`.`{$this->CNF['FSJ_ID']}` = `m`.`{$this->CNF['FIELD_ID']}` 
+                                           FROM `{$this->CNF['FSJ_PROFILE_ID']}`=:profile_id", $this->CNF['FIELD_ID'], array('profile_id' => $iProfileId));
+    }
+
+    function getSavedJotInLots($iProfileId, $aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aWhere['start'] = isset($aParams['start']) ? (int) $aParams['start'] : 0;
+        $aWhere['per_page'] = isset($aParams['per_page']) ? (int) $aParams['per_page'] : (int) $this->CNF['MAX_LOTS_NUMBER'];
+        $sLimit = "LIMIT :start, :per_page";
+
+        $sWhere = '';
+        if (isset($aParams['term'])) {
+            $aParams = preg_split('/[\s]/', $aParams['term'], -1, PREG_SPLIT_NO_EMPTY);
+            $sCriteria = implode('%', $aParams);
+            $aWhere['criteria'] = "%{$sCriteria}%";
+            $sWhere = "AND `m`.`{$CNF['FIELD_MESSAGE']}` like :criteria";
+        }
+
+        return $this->getAll("SELECT `m`.`{$this->CNF['FIELD_MESSAGE_FK']}`,`l`.* FROM `{$this->CNF['TABLE_SAVED_JOTS']}` as `s`
+                                        LEFT JOIN `{$this->CNF['TABLE_MESSAGES']}` as `m` ON `s`.`{$this->CNF['FSJ_ID']}` = `m`.`{$this->CNF['FIELD_ID']}`
+                                        LEFT JOIN `{$this->CNF['TABLE_ENTRIES']}` as `l` ON `l`.`{$this->CNF['FIELD_ID']}` = `m`.`{$this->CNF['FIELD_MESSAGE_FK']}`
+                                        WHERE `{$this->CNF['FSJ_PROFILE_ID']}`=:profile_id {$sWhere}
+                                        GROUP BY `m`.`{$this->CNF['FIELD_MESSAGE_FK']}`
+                                        {$sLimit}", ['profile_id' => $iProfileId] + $aWhere);
+    }
+
+    public function getJotReplies($iLotId, $bCount = false)
+    {
+        $aWhere = array('id' => $iLotId);
+
+        if ($bCount)
+            return $this->getOne("SELECT COUNT(*) FROM `{$this->CNF['TABLE_MESSAGES']}` WHERE `{$this->CNF['FIELD_MESSAGE_FK']}`=:id", $aWhere);
+
+        return $this->getAll("SELECT * FROM `{$this->CNF['TABLE_MESSAGES']}` 
+                                WHERE `{$this->CNF['FIELD_MESSAGE_FK']}`=:id 
+                                ORDER BY `{$this->CNF['FIELD_MESSAGE_ID']}`
+                                ", $aWhere);
+    }
+
+    function getReactionsMentionsLots($iProfileId, $aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aWhere['start'] = isset($aParams['start']) ? (int) $aParams['start'] : 0;
+        $aWhere['per_page'] = isset($aParams['per_page']) ? (int) $aParams['per_page'] : (int) $this->CNF['MAX_LOTS_NUMBER'];
+        $sLimit = "LIMIT :start, :per_page";
+
+        $sUrl = BxDolProfile::getInstance($iProfileId)->getUrl();
+        $aWhere['parts'] = '(^|,)' . (int) $iProfileId . '(,|$)';
+
+        $sWhere = '';
+        if (isset($aParams['term'])) {
+            $aParams = preg_split('/[\s]/', $aParams['term'], -1, PREG_SPLIT_NO_EMPTY);
+            $sCriteria = implode('%', $aParams);
+            $aWhere['criteria'] = "%{$sCriteria}%";
+            $sWhere = "AND `m`.`{$CNF['FIELD_MESSAGE']}` like :criteria";
+        }
+
+        return $this->getAll("SELECT `m`.`{$CNF['FIELD_MESSAGE_FK']}`, `l`.* 
+               FROM `{$CNF['TABLE_MESSAGES']}` as `m`
+               LEFT JOIN `{$CNF['TABLE_ENTRIES']}` as `l` ON `l`.`{$CNF['FIELD_ID']}` = `m`.`{$CNF['FIELD_MESSAGE_FK']}`
+               LEFT JOIN `{$CNF['TABLE_JOT_REACTIONS']}` as `r` ON `m`.`{$CNF['FIELD_MESSAGE_ID']}` = `r`.`{$CNF['FIELD_REACT_JOT_ID']}`
+               WHERE ((`r`.`{$CNF['FIELD_REACT_JOT_ID']}` IS NOT NULL AND `m`.`{$CNF['FIELD_MESSAGE_AUTHOR']}`=:profile_id ) 
+                            OR `m`.`{$CNF['FIELD_MESSAGE']}` LIKE '\"%" . '/' . bx_ltrim_str($sUrl, BX_DOL_URL_ROOT) . "\"' ) AND 
+                            (`l`.`{$CNF['FIELD_PARTICIPANTS']}` REGEXP :parts OR `l`.`{$CNF['FIELD_AUTHOR']}`=:profile_id) {$sWhere}
+                                    GROUP BY `m`.`{$CNF['FIELD_MESSAGE_FK']}`
+                                    {$sLimit}", ['profile_id' => $iProfileId] + $aWhere);
+    }
+
+    function getLotsWithReplies($iProfileId, $aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aWhere['start'] = isset($aParams['start']) ? (int) $aParams['start'] : 0;
+        $aWhere['per_page'] = isset($aParams['per_page']) ? (int) $aParams['per_page'] : (int) $this->CNF['MAX_LOTS_NUMBER'];
+        $sLimit = "LIMIT :start, :per_page";
+
+        $sWhere = '';
+        if (isset($aParams['term'])) {
+            $aParams = preg_split('/[\s]/', $aParams['term'], -1, PREG_SPLIT_NO_EMPTY);
+            $sCriteria = implode('%', $aParams);
+            $aWhere['criteria'] = "%{$sCriteria}%";
+            $sWhere = "AND `m`.`{$CNF['FIELD_MESSAGE']}` like :criteria";
+        }
+
+        return $this->getAll("SELECT `m`.`{$CNF['FIELD_MESSAGE_FK']}`, `l`.*
+                                    FROM `{$this->CNF['TABLE_MESSAGES']}` as `m` 
+                                    LEFT JOIN `{$this->CNF['TABLE_ENTRIES']}` as `l` ON `l`.`{$this->CNF['FIELD_ID']}` = `m`.`{$this->CNF['FIELD_MESSAGE_FK']}`
+                                    LEFT JOIN `{$CNF['TABLE_MESSAGES']}` as `r` ON `r`.`{$CNF['FIELD_MESSAGE_ID']}` = `m`.`{$CNF['FIELD_MESSAGE_REPLY']}`
+                                    WHERE `r`.`{$CNF['FIELD_MESSAGE_AUTHOR']}`=:profile_id AND `m`.`{$CNF['FIELD_MESSAGE_REPLY']}` <> 0 {$sWhere}
+                                    GROUP BY `m`.`{$CNF['FIELD_MESSAGE_FK']}`
+                                    {$sLimit}", ['profile_id' => $iProfileId] + $aWhere);
+    }
+
+    function registerGroup($sUrl, $iLotId, $bForce = false)
+    {
+        $aUrl = [];
+        parse_str($sUrl, $aUrl);
+        if (!empty($aUrl) && isset($aUrl['i'])) {
+            $oPage = BxDolPage::getObjectInstanceByModuleAndURI(BX_IM_EMPTY_URL, $aUrl['i']);
+
+            if (!$oPage && !$bForce)
+                return false;
+
+            $sModule = '';
+            $sTitle = _t('_bx_messenger_page_unknown');
+            $iAuthor = $iProfileId = 0;
+            $iPrivacy = BX_DOL_PG_ALL;
+            if ($oPage && ($aPageInfo = $oPage->getObject()) && ($sModule = $oPage->getModule()) && ($oModule = BxDolModule::getInstance($sModule))) {
+                $CNF = &$oModule->_oConfig->CNF;
+                $sTitle = _t($aPageInfo['title']);
+                $iAuthor = $aPageInfo['author'];
+                if (isset($aUrl['id']) && !empty($oModule) && ($aResult = bx_srv($sModule, 'get_info', array($aUrl['id'], false)))) {
+                    $sTitle = $aResult[$CNF['FIELD_TITLE']];
+                    $iAuthor = $aResult[$CNF['FIELD_AUTHOR']];
+                    $iProfileId = isset($aResult['profile_id']) ? $aResult['profile_id'] : 0;
+                    $iPrivacy = isset($aResult[$CNF['FIELD_ALLOW_VIEW_TO']]);
+                }
+            }
+
+            if (
+                $this->query(
+                    "INSERT INTO `{$this->CNF['TABLE_GROUPS']}`
+                                                 SET `{$this->CNF['FMG_NAME']}` = :name, 
+                                                     `{$this->CNF['FMG_PRIVACY']}` = :privacy,
+                                                     `{$this->CNF['FMG_AUTHOR']}` = :author,
+                                                     `{$this->CNF['FMG_URL']}` = :url,
+                                                     `{$this->CNF['FMG_MODULE']}` = :module,
+                                                     `{$this->CNF['FMG_PROFILE_ID']}` =:profile_id,
+                                                     `{$this->CNF['FMG_ADDED']}` = UNIX_TIMESTAMP()",
+                    array(
+                        'name' => $sTitle,
+                        'author' => $iAuthor,
+                        'url' => $sUrl,
+                        'module' => in_array($sModule, $this->CNF['ADD-TO-PAGES-AREA'][BX_MSG_TALK_TYPE_PAGES]) ? BX_MSG_TALK_TYPE_PAGES : $sModule,
+                        'profile_id' => $iProfileId,
+                        'privacy' => $iPrivacy
+                    )
+                )
+            )
+
+                return $this->addLotToGroup($iLotId, $this->lastId());
+        }
+
+
+        return false;
+    }
+
+    function getMyLotsByGroups($iProfileId, $bCount = false)
+    {
+        $aWhere = array('profile' => (int) $iProfileId, 'parts' => '(^|,)' . (int) $iProfileId . '(,|$)');
+
+        if ($bCount)
+            $sCount = "SQL_CALC_FOUND_ROWS";
+
+        $aGroups = $this->getAll("SELECT 
+                                            `l`.*, `g`.`{$this->CNF['FMG_PROFILE_ID']}`,`g`.`{$this->CNF['FMG_MODULE']}`, `g`.`{$this->CNF['FMG_NAME']}`, `gl`.`{$this->CNF['FMGL_GROUP_ID']}`, 
+                                             IF (`l`.`{$this->CNF['FIELD_UPDATED']}` = 0, `l`.`{$this->CNF['FIELD_ADDED']}`, `l`.`{$this->CNF['FIELD_UPDATED']}`) as `order`,
+                                             IF (`l`.`{$this->CNF['FIELD_URL']}` = '', `g`.`{$this->CNF['FMG_URL']}`, `l`.`{$this->CNF['FIELD_URL']}`) as `gurl`,
+                                             `un`.`{$this->CNF['FIELD_NEW_UNREAD']}` as `unread`
+                                             FROM `{$this->CNF['TABLE_ENTRIES']}` as `l`                                                 
+                                             LEFT JOIN `{$this->CNF['TABLE_NEW_MESSAGES']}` as `un` ON `l`.`{$this->CNF['FIELD_ID']}` = `un`.`{$this->CNF['FIELD_NEW_LOT']}` AND `un`.`{$this->CNF['FIELD_NEW_PROFILE']}`=:profile 
+                                             LEFT JOIN `{$this->CNF['TABLE_GROUPS_LOTS']}` as `gl` ON `gl`.`{$this->CNF['FMGL_LOT_ID']}` = `l`.`{$this->CNF['FIELD_ID']}`
+                                             LEFT JOIN `{$this->CNF['TABLE_GROUPS']}` as `g` ON `g`.`{$this->CNF['FMG_ID']}` = `gl`.`{$this->CNF['FMGL_GROUP_ID']}`
+                                             WHERE (`l`.`{$this->CNF['FIELD_PARTICIPANTS']}` REGEXP :parts OR `l`.`{$this->CNF['FIELD_AUTHOR']}`=:profile) AND `gl`.`{$this->CNF['FMGL_GROUP_ID']}` IS NOT NULL
+                                             ORDER BY `g`.`{$this->CNF['FMG_MODULE']}`", $aWhere);
+
+        // print_r($aGroups);
+        $aResult = array();
+        $sModule = '';
+        foreach ($aGroups as &$aGroup) {
+            if (!isset($aGroup[$this->CNF['FMG_MODULE']]))
+                continue;
+
+            if ($aGroup[$this->CNF['FMG_MODULE']] != $sModule)
+                $sModule = $aGroup[$this->CNF['FMG_MODULE']];
+
+            if (!isset($aResult[$sModule][$aGroup[$this->CNF['FMGL_GROUP_ID']]])) {
+                $aResult[$sModule][$aGroup[$this->CNF['FMGL_GROUP_ID']]] = [
+                    $this->CNF['FMG_NAME'] => $aGroup[$this->CNF['FMG_NAME']],
+                    $this->CNF['FMG_ID'] => $aGroup[$this->CNF['FMG_PROFILE_ID']], // group profile id of the module
+                    $this->CNF['FMGL_GROUP_ID'] => $aGroup[$this->CNF['FMGL_GROUP_ID']],
+                    $this->CNF['FMG_URL'] => $aGroup['gurl']
+                ];
+            }
+
+            // fill each groups with existed talks in it group
+            $aResult[$sModule][$aGroup[$this->CNF['FMGL_GROUP_ID']]][$this->CNF['GROUPS_ITEMS_FIELD']][] = [
+                $this->CNF['FIELD_ADDED'] => $aGroup[$this->CNF['FIELD_ADDED']],
+                $this->CNF['FIELD_AUTHOR'] => $aGroup[$this->CNF['FIELD_AUTHOR']],
+                $this->CNF['FIELD_TITLE'] => $aGroup[$this->CNF['FIELD_TITLE']],
+                $this->CNF['FIELD_PARTICIPANTS'] => $aGroup[$this->CNF['FIELD_PARTICIPANTS']],
+                $this->CNF['FIELD_ID'] => $aGroup[$this->CNF['FIELD_ID']],
+                $this->CNF['FMG_URL'] => $aGroup[$this->CNF['FIELD_URL']]
+                //$this->CNF['FMG_PROFILE_ID'] => $aGroup[$this->CNF['FMG_PROFILE_ID']],
+            ];
+        }
+
+        return $aResult;
+    }
+
+    function getGroupIdByLotId($iLotId)
+    {
+        return $this->getOne("SELECT `{$this->CNF['FMGL_GROUP_ID']}` 
+							FROM `{$this->CNF['TABLE_GROUPS_LOTS']}`								
+							WHERE `{$this->CNF['FMGL_LOT_ID']}`=:id", array('id' => $iLotId));
+    }
+
+    function getTalksByGroupId($iGroupId)
+    {
+        return $this->getAllWithKey("SELECT `l`.* 
+								            FROM `{$this->CNF['TABLE_GROUPS_LOTS']}` as `gl` 
+								            LEFT JOIN `{$this->CNF['TABLE_ENTRIES']}` as `l` ON `gl`.`{$this->CNF['FMGL_LOT_ID']}` = `l`.`{$this->CNF['FIELD_ID']}`
+								            WHERE `gl`.`{$this->CNF['FMGL_GROUP_ID']}`=:id AND `l`.`id` IS NOT NULL
+								            ", $this->CNF['FIELD_ID'], array('id' => $iGroupId));
+    }
+
+    function getGroupById($iGroupId)
+    {
+        return $this->getRow("SELECT *  
+							FROM `{$this->CNF['TABLE_GROUPS']}`								
+							WHERE `{$this->CNF['FMG_ID']}`=:id LIMIT 1", array('id' => $iGroupId));
+    }
+
+    function getGroupByUrl($sUrl)
+    {
+        return $this->getRow("SELECT * FROM `{$this->CNF['TABLE_GROUPS']}` WHERE `{$this->CNF['FMG_URL']}`=:url LIMIT 1", array('url' => $sUrl));
+    }
+
+    function getLotType($iLotId)
+    {
+        $aInfo = $this->getLotInfoById($iLotId);
+
+        if (empty($aInfo))
+            return false;
+
+        if ($aInfo[$this->CNF['FMGL_GROUP_ID']]) {
+            $aGroupInfo = $this->getGroup($aInfo[$this->CNF['FMGL_GROUP_ID']]);
+            return [
+                'group_id' => $aInfo[$this->CNF['FMGL_GROUP_ID']],
+                'type' => BX_MSG_TALK_TYPE_GROUPS,
+                'groups_type' => $aGroupInfo[$this->CNF['FMG_MODULE']]
+            ];
+        }
+
+        if ((int) $aInfo[$this->CNF['FIELD_PARENT_JOT']])
+            return ['type' => BX_MSG_TALK_TYPE_THREADS];
+
+        if ($aInfo[$this->CNF['FIELD_TYPE']] == BX_IM_TYPE_PRIVATE)
+            return ['type' => BX_MSG_TALK_TYPE_DIRECT];
+
+        if ($aInfo[$this->CNF['FIELD_TYPE']] == BX_IM_TYPE_BROADCAST)
+            return ['type' => BX_MSG_TALK_TYPE_BROADCAST];
+
+        return false;
+    }
+
+    function getUnreadMessagesStat($iProfileId)
+    {
+        $aItems = $this->getLotsWithUnreadMessages($iProfileId);
+
+        $aResult = [];
+        if (empty($aItems))
+            return $aResult;
+
+        foreach ($aItems as $iLotId => $iCount) {
+            if (!($aType = $this->getLotType($iLotId)))
+                continue;
+
+            if (isset($aType['group_id']))
+                $aResult['groups'][$aType['groups_type']][$aType['group_id']][$iLotId] = +$iCount;
+            else
+                $aResult[$aType['type']][$iLotId] = +$iCount;
+        }
+
+        return array_merge($aResult, [BX_MSG_TALK_TYPE_INBOX => count($aItems)]);
+    }
+
+    function addMediaTrack($iFileId, $iProfileId)
+    {
+        return $this->query("REPLACE INTO `{$this->CNF['TABLE_JOTS_MEDIA_TRACKER']}`					
+							       SET `{$this->CNF['FJMT_FILE_ID']}`=:id, 
+                                       `{$this->CNF['FJMT_USER_ID']}`=:user_id", ['id' => $iFileId, 'user_id' => $iProfileId]);
+    }
+
+    function isFileCollapsed($iFileId, $iProfileId)
+    {
+        return $this->getOne(
+            "SELECT COUNT(*) FROM `{$this->CNF['TABLE_JOTS_MEDIA_TRACKER']}`					
+							        WHERE `{$this->CNF['FJMT_FILE_ID']}`=:id AND `{$this->CNF['FJMT_USER_ID']}`=:user_id",
+            ['id' => $iFileId, 'user_id' => $iProfileId]
+        );
+    }
+
+    function removeMediaTracker($iFileId = 0, $iProfileId = 0)
+    {
+        if (!$iFileId && !$iProfileId)
+            return false;
+
+        $aWhereParams = $aWhere = [];
+        if ($iFileId) {
+            $aWhere[] = "`{$this->CNF['FJMT_FILE_ID']}`=:id";
+            $aWhereParams['id'] = $iFileId;
+        }
+
+        if ($iProfileId) {
+            $aWhere[] = "`{$this->CNF['FJMT_USER_ID']}`=:user_id";
+            $aWhereParams['user_id'] = $iProfileId;
+        }
+
+        $sWhere = implode(' AND ', $aWhere);
+        return $this->query("DELETE FROM `{$this->CNF['TABLE_JOTS_MEDIA_TRACKER']}`					
+							       WHERE {$sWhere}", $aWhereParams);
+    }
+
+    public function getObjectAuthorId($iId)
+    {
+        $sQuery = $this->prepare("SELECT `{$this->CNF['FIELD_MESSAGE_AUTHOR']}` FROM `{$this->CNF['TABLE_MESSAGES']}` WHERE `{$this->CNF['FIELD_MESSAGE_ID']}` = ? LIMIT 1", $iId);
+        return (int) $this->getOne($sQuery);
+    }
+
+    public function isPerformed($iObjectId, $iAuthorId, $sEmoji)
+    {
+        $sQuery = $this->prepare("SELECT COUNT(*) 
+                                         FROM `{$this->CNF['TABLE_JOT_REACTIONS']}` 
+                                         WHERE `{$this->CNF['FIELD_REACT_JOT_ID']}` = ? AND `{$this->CNF['FIELD_REACT_PROFILE_ID']}` = ? AND 
+                                               `{$this->CNF['FIELD_REACT_EMOJI_ID']}` = ? LIMIT 1", $iObjectId, $iAuthorId, $sEmoji);
+        return (int) $this->getOne($sQuery) != 0;
+    }
+
+    public function putVote($iObjectId, $iAuthorId, $sAuthorIp, $aData, $bUndo = false)
+    {
+        $sReaction = $aData['reaction'];
+        $aReactions = BxDolFormQuery::getDataItems('sys_vote_reactions', false, BX_DATA_VALUES_ALL);
+        if (!isset($aReactions[$sReaction]))
+            return false;
+
+        if (!$aReaction = @unserialize($aReactions[$sReaction]['Data']))
+            return false;
+
+        $sQuery = "SELECT `id` FROM `{$this->CNF['TABLE_JOT_REACTIONS']}` WHERE `{$this->CNF['FIELD_REACT_JOT_ID']}` = :object_id 
+                           AND `{$this->CNF['FIELD_REACT_EMOJI_ID']}` = :reaction AND `{$this->CNF['FIELD_REACT_PROFILE_ID']}`=:author   LIMIT 1";
+        $bExists = (int) $this->getOne($sQuery, array('object_id' => $iObjectId, 'reaction' => $sReaction, 'author' => $iAuthorId)) != 0;
+
+        if ($bExists && $bUndo)
+            return $this->deleteReaction($iObjectId, $iAuthorId, $aData['reaction']);
+        else
+            return $this->addJotReaction($iObjectId, $iAuthorId, ['id' => $this->_oConfig->convertApp2Emoji($aData['reaction']), 'native' => $aReaction['emoji']]);
+
+        return $this->lastId();
+    }
+
+    public function getVote($iObjectId)
+    {
+        $aReactions = BxDolFormQuery::getDataItems('sys_vote_reactions', false, BX_DATA_VALUES_ALL);
+        $aReactionsGot = $this->getPairs("SELECT COUNT(*) as `count`, `{$this->CNF['FIELD_REACT_EMOJI_ID']}` FROM `{$this->CNF['TABLE_JOT_REACTIONS']}` WHERE `{$this->CNF['FIELD_REACT_JOT_ID']}` = :object_id GROUP BY `{$this->CNF['FIELD_REACT_EMOJI_ID']}`", $this->CNF['FIELD_REACT_EMOJI_ID'], 'count', array('object_id' => $iObjectId));
+        $aResult = array();
+        foreach ($aReactions as $sName => $iValue) {
+            $iCount = $iSum = 0;
+            if (!empty($aReactionsGot[$sName])) {
+                $iCount = (int) $aReactionsGot[$sName];
+                $iSum += $iCount;
+            }
+
+            $aResult['count_' . $sName] = $iCount;
+            $aResult['sum_' . $sName] = $iSum;
+            $aResult['rate_' . $sName] = $iCount != 0 ? $iSum / $iCount : 0;
+        }
+
+        $aResult['count_default'] = $iCount;
+        $aResult['sum_default'] = $iSum;
+
+        return $aResult;
+    }
+
+    public function getTrack($iObjectId, $iAuthorId)
+    {
+        return $this->getRow("SELECT *, `{$this->CNF['FIELD_REACT_EMOJI_ID']}` as `reaction` FROM `{$this->CNF['TABLE_JOT_REACTIONS']}` WHERE `{$this->CNF['FIELD_REACT_JOT_ID']}` = :object_id AND `{$this->CNF['FIELD_REACT_PROFILE_ID']}` = :author_id LIMIT 1", array(
+            'object_id' => $iObjectId,
+            'author_id' => $iAuthorId
+        ));
+    }
+
+    public function getPerformed($aParams = array())
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $aBindings = array();
+
+        $sSelectClause = '`tt`.*';
+        $sJoinClause = $sWhereClause = '';
+        $sLimitClause = isset($aParams['start']) && !empty($aParams['per_page']) ? "LIMIT " . $aParams['start'] . ", " . $aParams['per_page'] : "";
+
+        if (!empty($aParams['type']))
+            switch ($aParams['type']) {
+                case 'by':
+                    $aBindings['object_id'] = $aParams['object_id'];
+
+                    $sJoinClause = "INNER JOIN `sys_profiles` AS `tp` ON `tt`.`user_id`=`tp`.`id` AND `tp`.`status`='active'";
+                    $sWhereClause = "AND `tt`.`jot_id` = :object_id";
+
+                    if (!empty($aParams['reaction'])) {
+                        $aMethod['name'] = 'getColumn';
+                        $aBindings['reaction'] = $this->_oConfig->convertApp2Emoji($aParams['reaction']);
+
+                        $sSelectClause = "`tt`.`user_id`";
+                        $sWhereClause .= " AND `tt`.`emoji_id`=:reaction";
+                    } else {
+                        $aMethod['name'] = 'getAll';
+
+                        $sSelectClause = "`tt`.`user_id`, `tt`.`emoji_id`";
+                    }
+                    break;
+            }
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `{$this->CNF['TABLE_JOT_REACTIONS']}` AS `tt` " . $sJoinClause . " WHERE 1 " . $sWhereClause . $sLimitClause;
+        $aMethod['params'][] = $aBindings;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    /**
+     * Occurs when delete context object
+     * @param $iAuthorId context id (group,space ) and etc...
+     */
+    function deleteAuthorEntries($iAuthorId)
+    {
+        return false;
+    }
+
+    public function pruningByDate($iDate)
+    {
+        return 0;
+    }
+
+    function updateConvoHash($iConvoId)
+    {
+        $sHash = $this->_oConfig->generateConvoHash($iConvoId);
+        return $this->query("UPDATE `{$this->CNF['TABLE_ENTRIES']}` SET `{$this->CNF['FIELD_HASH']}`=:hash 
+                                            WHERE `{$this->CNF['FIELD_ID']}`=:id ", ['hash' => $sHash, 'id' => $iConvoId]);
+    }
+
+    function updateConvoFiled($iConvoId, $sField, $mixedValue)
+    {
+        if (!$sField)
+            return false;
+
+        return $this->query("UPDATE `{$this->CNF['TABLE_ENTRIES']}` 
+                             SET `{$sField}`=:value 
+                             WHERE `{$this->CNF['FIELD_ID']}`=:id", ['value' => $mixedValue, 'id' => $iConvoId]);
+    }
+
+    function getConvoByHash($sHash, $bIdOnly = true)
+    {
+        $aConvo = $this->getRow("SELECT * FROM `{$this->CNF['TABLE_ENTRIES']}` WHERE `{$this->CNF['FIELD_HASH']}`=:hash LIMIT 1", ['hash' => $sHash]);
+
+        return $bIdOnly && !empty($aConvo) ? $aConvo[$this->CNF['FIELD_ID']] : $aConvo;
+    }
+
+    private function getProfilesByCriteriaSqlParts($aFields)
+    {
+        $aSqlParts = ['where' => '', 'join' => ''];
+        if (isset($aFields['membership'])) {
+            $aSqlParts = BxDolAclQuery::getInstance()->getContentByLevelAsSQLPart('sys_profiles', 'id', $aFields['membership']);
+            unset($aFields['membership']);
+        }
+
+        if (!empty($aFields)) {
+            $aSqlParts['join'] .= "LEFT JOIN `bx_persons_data` as `pd` ON `pd`.`id` = `sys_profiles`.`content_id` AND `sys_profiles`.`type` = 'bx_persons'";
+            foreach ($aFields as $sName => $mixedValues) {
+                if ($sName === 'location' && !empty($mixedValues)) {
+                    $sLocation = implode("','", $mixedValues);
+                    $aSqlParts['join'] .= "LEFT JOIN `bx_persons_meta_locations` as `pdl` ON `pdl`.`object_id` = `pd`.`id`";
+                    $aSqlParts['where'] .= " AND `pdl`.`country` IN ('" . $sLocation . "')";
+                    continue;
+                }
+
+                if (is_array($mixedValues))
+                    $aSqlParts['where'] .= " AND `pd`.`{$sName}` IN ('" . implode("','", $mixedValues) . "')";
+                else
+                    $aSqlParts['where'] .= " AND `pd`.`{$sName}` = '{$mixedValues}'";
+            }
+        }
+
+        bx_alert($this->_oConfig->getObject('alert'), 'broadcast_criteria_after', 0, 0, [
+            'fields' => $aFields,
+            'sql' => &$aSqlParts
+        ]);
+
+        return $aSqlParts;
+    }
+
+    public function getProfilesByCriteria($aFields, $aAddons = [])
+    {
+        return $this->_getProfilesByCriteria($aFields, $aAddons, false);
+    }
+
+    public function getProfilesCountByCriteria($aFields, $aAddons = [])
+    {
+        return $this->_getProfilesByCriteria($aFields, $aAddons, true);
+    }
+
+    private function _getProfilesByCriteria($aFields, $aAddons = [], $bCountOnly = false)
+    {
+        if (empty($aFields) && empty($aAddons)) {
+            return $bCountOnly ? 0 : [];
+        }
+
+        $aExcludeProfiles = $aAddons['exclude_profiles'] ?? [];
+        $aIncludeProfiles = $aAddons['include_profiles'] ?? [];
+
+        $aQueries = [];
+        if (!empty($aFields) || empty($aIncludeProfiles)) {
+            $aSqlParts = $this->getProfilesByCriteriaSqlParts($aFields);
+            $sWhereClause = !empty($aSqlParts['where']) ? $aSqlParts['where'] : '';
+
+            if (!empty($aExcludeProfiles)) {
+                $sWhereClause .= " AND `sys_profiles`.`id` NOT IN (" . implode(',', $aExcludeProfiles) . ")";
+            }
+
+            $aQueries[] = "SELECT DISTINCT `sys_profiles`.`id` FROM `sys_profiles`" . $aSqlParts['join'] . " WHERE 1" . $sWhereClause;
+        }
+
+        if (!empty($aIncludeProfiles)) {
+            $sExcludeClause = '';
+            if (!empty($aExcludeProfiles)) {
+                $sExcludeClause = " AND `sys_profiles`.`id` NOT IN (" . implode(',', $aExcludeProfiles) . ")";
+            }
+            $sQueriesParts = implode(',', $aIncludeProfiles);
+            $aQueries[] = "SELECT `sys_profiles`.`id` FROM `sys_profiles` WHERE `sys_profiles`.`id` IN ({$sQueriesParts})" . $sExcludeClause;
+        }
+
+        $sSql = implode(" UNION ", $aQueries);
+
+        if ($bCountOnly) {
+            $sSql = "SELECT COUNT(*) FROM ({$sSql}) AS `t`";
+            return (int)$this->getOne($this->prepare($sSql));
+        } else {
+            return $this->getColumn($this->prepare($sSql));
+        }
+    }
+
+    function createBroadcastUsers($iConvoId, &$aData)
+    {
+        $sBegin = "REPLACE INTO `{$this->CNF['TABLE_MASS_TRACKER']}` 
+                          (`{$this->CNF['FIELD_MASS_CONVO_ID']}`, `{$this->CNF['FIELD_MASS_USER_ID']}`) 
+                   VALUES ";
+
+        $bExecuted = false;
+        $aProfileChunks = array_chunk($aData, 200);
+        foreach ($aProfileChunks as &$aChunk) {
+            $aValues = array_map(function ($iProfileId) use ($iConvoId) {
+                return "($iConvoId, $iProfileId)";
+            }, $aChunk);
+
+            if (!empty($aValues)) {
+                $sBegin . implode(",", $aValues);
+                $bExecuted |= (bool) $this->query($sBegin . implode(",", $aValues));
+            }
+        };
+
+        return $bExecuted;
+    }
+
+    function getBroadcastParticipants($iConvoId, $iLimit = 0, $iAfterUserId = 0)
+    {
+        $aBindings = ['id' => $iConvoId];
+        $sWhere = "WHERE `{$this->CNF['FIELD_MASS_CONVO_ID']}`=:id";
+        if ((int) $iAfterUserId > 0) {
+            $aBindings['after_user_id'] = (int) $iAfterUserId;
+            $sWhere .= " AND `{$this->CNF['FIELD_MASS_USER_ID']}` > :after_user_id";
+        }
+
+        $sLimit = (int) $iLimit > 0 ? " LIMIT " . (int) $iLimit : "";
+        return $this->getColumn("SELECT `{$this->CNF['FIELD_MASS_USER_ID']}` 
+                                            FROM `{$this->CNF['TABLE_MASS_TRACKER']}` 
+                                            {$sWhere}
+                                            ORDER BY `{$this->CNF['FIELD_MASS_USER_ID']}` ASC{$sLimit}", $aBindings);
+    }
+
+    function isBroadcastParticipant($iConvoId, $iProfileId)
+    {
+        return $this->getOne("SELECT  COUNT(*)
+                                          FROM `{$this->CNF['TABLE_MASS_TRACKER']}` 
+                                          WHERE `{$this->CNF['FIELD_MASS_CONVO_ID']}`=:id AND `{$this->CNF['FIELD_MASS_USER_ID']}`=:user_id", ['id' => $iConvoId, 'user_id' => $iProfileId]) != 0;
+    }
+
+    function getNotificationsSettings($sGroup)
+    {
+        if (!$this->isModuleByName('bx_notifications'))
+            return false;
+
+        return $this->getPairs("SELECT `delivery`, `active` FROM `bx_notifications_settings` WHERE `group`=:group", 'delivery', 'active', ['group' => $sGroup]);
+    }
+
+    /**
+     * For API only.
+     */
+    public function getJotsByLotIdApi($aData)
+    {
+        $sMode = isset($aData['mode']) ? $aData['mode'] : 'new';
+        $iLot = isset($aData['lot']) ? (int) $aData['lot'] : BX_IM_EMPTY;
+        $iStart = isset($aData['start']) ? (int) $aData['start'] : BX_IM_EMPTY;
+        $iLimit = isset($aData['limit']) ? (int) $aData['limit'] : BX_IM_EMPTY;
+        $sArea = isset($aData['area']) ? $aData['area'] : '';
+
+        $aBindings = [
+            'lot' => (int) $iLot,
+            'start' => (int) $iStart,
+            'limit' => (int) $iLimit
+        ];
+
+        $sWhere = $sLimit = '';
+        $sWhere = "AND `m`.`{$this->CNF['FIELD_MESSAGE_FK']}` = :lot ";
+
+        $sJoinArea = $sWhereArea = '';
+        if ($sArea)
+            list($sJoinArea, $sWhereArea) = $this->getJotsByArea($sArea);
+
+        $sQuery = "SELECT `m`.*  FROM `{$this->CNF['TABLE_MESSAGES']}` as `m` {$sJoinArea} WHERE 1 {$sWhere} {$sWhereArea}	ORDER BY `m`.`{$this->CNF['FIELD_MESSAGE_ID']}` DESC LIMIT :start, :limit";
+        return $this->getAll("({$sQuery}) ORDER BY `{$this->CNF['FIELD_MESSAGE_ID']}`", $aBindings);
+    }
+
+    public function getVideoConferenceInfoByRoom($sRoom, $iLimit = 0)
+    {
+        if (!$sRoom)
+            return [];
+
+        $sLimit = '';
+        $aParams = ['room' => $sRoom];
+        if ($iLimit && is_numeric($iLimit)) {
+            $limit = (int) $iLimit;
+            if ($limit > 0) {
+                $sLimit = "LIMIT {$limit}";
+            }
+        }
+
+        return $this->getAll("SELECT `v`.`{$this->CNF['FJVC_ID']}`, 
+                                           `v`.`{$this->CNF['FJVC_LOT_ID']}`,`v`.`{$this->CNF['FJVC_ACTIVE']}`, 
+                                           `vt`.`{$this->CNF['FJVCT_ID']}` as `conferance_id`,
+                                           `vt`.`{$this->CNF['FJVCT_START']}`,`vt`.`{$this->CNF['FJVCT_END']}`,
+                                           `vt`. `{$this->CNF['FJVCT_PART']}`, `j`.`{$this->CNF['FIELD_MESSAGE_ID']}` as `jot_id`
+                                    FROM `{$this->CNF['TABLE_JVC']}` as `v`
+                                    INNER JOIN `{$this->CNF['TABLE_JVCT']}` as `vt` ON `v`.`{$this->CNF['FJVC_ID']}`=`vt`.`{$this->CNF['FJVCT_FK']}`
+                                    LEFT JOIN `{$this->CNF['TABLE_MESSAGES']}` as `j` ON `j`.`{$this->CNF['FIELD_MESSAGE_VIDEOC']}` = `vt`.`{$this->CNF['FJVCT_ID']}`
+                                    WHERE `v`.`{$this->CNF['FJVC_ROOM']}`=:room
+                                    ORDER BY `vt`.`{$this->CNF['FJVCT_ID']}` DESC {$sLimit}", $aParams);
+    }
+}
+
+/** @} */

+ 119 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerLotInfoMenu.php

@@ -0,0 +1,119 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup	Messenger Messenger
+ * @ingroup		UnaModules
+ *
+ * @{
+ */
+
+/**
+ * View block menu.
+ */
+class BxMessengerLotInfoMenu extends BxBaseModTextMenu
+{
+    protected $_oModule;
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this-> MODULE = 'bx_messenger';
+        $this->_oModule = BxDolModule::getInstance($this-> MODULE);
+        parent::__construct($aObject, $this->_oModule->_oTemplate);
+    }
+
+    public function setContentId($iContentId)
+    {
+        parent::setContentId($iContentId);
+
+        $this->_aContentParams = array_merge((array)$this->_aContentParams, ['lot' => (int)$this->_iContentId]);
+    }
+
+    public function initContentParams()
+    {
+        parent::initContentParams();
+
+        $this->_aContentParams['lot'] = (int)$this->_iContentId;
+    }
+
+    /**
+     * API: the menu is requested on demand (system/get_menu) with the talk in params.
+     */
+    public function setContentParams($aParams)
+    {
+        if(empty($aParams['lot'])) {
+            return false;
+        }
+
+        $this->setContentId((int)$aParams['lot']);
+
+        return parent::setContentParams($aParams);
+    }
+
+    /**
+     * API: one menu for the talk header. Items of the talk menu (video call, star, mute, ...) go first,
+     * its 'settings' item (which opens this menu on the site) is left out. The site keeps both menus as they are.
+     */
+    public function getCodeAPI ()
+    {
+        $aResult = parent::getCodeAPI();
+        if(!$this->_iContentId) {
+            return $aResult;
+        }
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oMenuLot = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_TALK_MENU']);
+        if(!$oMenuLot) {
+            return $aResult;
+        }
+
+        $oMenuLot->setContentId($this->_iContentId);
+        $aMenuLot = $oMenuLot->getCodeAPI();
+        if(empty($aMenuLot['items']) || !is_array($aMenuLot['items'])) {
+            return $aResult;
+        }
+
+        $aItems = array_values(array_filter($aMenuLot['items'], function($aItem) {
+            return $aItem['name'] != 'settings';
+        }));
+
+        $aResult['items'] = array_merge($aItems, is_array($aResult['items']) ? $aResult['items'] : []);
+
+        return $aResult;
+    }
+
+    protected function _isVisible ($a)
+    {
+        $iProfileId = bx_get_logged_profile_id();
+        $aLotInfo = $this->_oModule->_oDb->getLotInfoById($this->_iContentId);
+        if (!$iProfileId || empty($aLotInfo))
+            return false;
+
+        $oModule = &$this->_oModule;
+        if(!parent::_isVisible($a) || !$this->_iContentId)
+            return false;
+
+        $this->addMarkers(array('id' => $this->_iContentId));
+        switch ($a['name']) {
+            case 'add_participants':
+            case 'delete':
+            case 'clear':
+            case 'settings':
+                return $oModule->_oDb->isAuthor($this->_iContentId, $iProfileId) || ($oModule->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $iProfileId) === true);
+
+        }
+
+        return true;
+    }
+
+    public function getCode(){
+        return $this->_oTemplate->parseHtmlByName('popup_trans.html', [
+            'id' => "lot-info-menu-{$this->_iContentId}",
+            'wrapper_class' => 'bx-popup-menu',
+            'wrapper_style' => 'display:none;',
+            'content' => parent::getCode()]);
+    }
+}
+
+/** @} */

+ 169 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerLotMenu.php

@@ -0,0 +1,169 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup	Messenger Messenger
+ * @ingroup		UnaModules
+ *
+ * @{
+ */
+
+/**
+ * View block menu.
+ */
+class BxMessengerLotMenu extends BxBaseModTextMenu
+{
+    protected $_oModule;
+    private $_sPopupTalOptions = '';
+    private $_iProfileId = 0;
+    private $_isBlockVersion = true;
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->MODULE = 'bx_messenger';
+        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+        $this->_iProfileId = bx_get_logged_profile_id();
+
+        parent::__construct($aObject, $this->_oModule->_oTemplate);
+    }
+    public function setMessengerType($bType){
+        $this->_isBlockVersion = $bType;
+    }
+
+    public function isActive($sName){
+        if (!isset($this->_aObject['menu_items']))
+            $this->_aObject['menu_items'] = $this->getMenuItemsRaw ();
+
+        return !empty($sName) && isset($this->_aObject['menu_items'][$sName]) && (int)$this->_aObject['menu_items'][$sName]['active'];
+    }
+
+    public function _getMenuItem ($aMenu)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $oModule = &$this->_oModule;
+
+        switch($aMenu['name']){
+            case 'mute':
+                    $bIsMuted = $oModule->_oDb->isMuted($this->_iContentId, $this->_iProfileId);
+                    $aMenu['title'] = $bIsMuted ? _t('_bx_messenger_lots_menu_mute_info_on') : _t('_bx_messenger_lots_menu_mute_info_off');
+                    $aMenu['icon'] = $bIsMuted ? $CNF['BELL_ICON_OFF'] : $CNF['BELL_ICON_ON'];
+                    $aMenu['value'] = +$bIsMuted;
+                break;
+            case 'star':
+                    $bIsFav = $oModule->_oDb->isStarred($this->_iContentId, $this->_iProfileId);
+                    $aMenu['icon'] = $CNF['STAR_ICON'] . ((int)$bIsFav ? ' fill' : '');
+                    $aMenu['title'] = !$bIsFav ? _t('_bx_messenger_lots_menu_star_on') : _t('_bx_messenger_lots_menu_star_off');
+                    $aMenu['value'] = +$bIsFav;
+                break;
+            default:
+                $aMenu['value'] = '';
+        }
+
+        $mixedResult = parent::_getMenuItem($aMenu);
+
+        // API: current state of toggles (star, mute) for the app.
+        if($this->_bIsApi && is_array($mixedResult)) {
+            $mixedResult['value'] = $aMenu['value'];
+        }
+
+        return $mixedResult;
+    }
+
+    public function getCode ()
+    {
+        return parent::getCode() . $this->_sPopupTalOptions;
+    }
+
+    public function setTemplateById ($iTemplateId)
+    {
+        if ($iTemplateId == BX_DB_MENU_TEMPLATE_TABS)
+            $this->_aObject['template'] = 'menu_messenger_talk_header_hor.html';
+        else
+            $this->_aObject['template'] = 'menu_messenger_talk_header_ver.html';
+    }
+
+    protected function _isVisible ($a)
+    {
+        if (!$this->_iProfileId)
+            return false;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $oModule = &$this->_oModule;
+        if(!parent::_isVisible($a) || !$this->_iContentId)
+            return false;
+
+        $aLotInfo = $this->_oModule->_oDb->getLotInfoById($this->_iContentId);
+        $aLotMenuSettings = $this->_oModule->_oDb->getLotSettings($this->_iContentId, $CNF['FLS_SETTINGS']);
+        if (!empty($aLotMenuSettings) && in_array($a['name'], $aLotMenuSettings))
+            return false;
+
+        $bAllowedEdit = $this->_oModule->_oDb->isAuthor($aLotInfo[$CNF['FIELD_ID']], $this->_iProfileId) || $this->_oModule->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true;
+        $bBroadcast = $aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_BROADCAST;
+        if (!$bAllowedEdit && $bBroadcast)
+            return false;
+
+        switch ($a['name']) {
+            case 'settings':
+                // API: the app gets the talk info menu merged into one menu, no popup is needed.
+                if ($this->_bIsApi) {
+                    return false;
+                }
+
+                if ($this->_iContentId)
+                    $this->addMarkers(array('lot_menu_id' => "lot-info-menu-{$this->_iContentId}"));
+
+                $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_TALK_INFO_MENU']);
+                $oMenu->setContentId($this->_iContentId);
+
+                $this->_sPopupTalOptions = $oMenu->getCode();
+                return true;
+
+            case 'parent':
+                if (isset($aLotInfo[$CNF['FIELD_PARENT_JOT']]) && (int)$aLotInfo[$CNF['FIELD_PARENT_JOT']]) {
+                    $iMainLotId = $this->_oModule->_oDb->getLotByJotId((int)$aLotInfo[$CNF['FIELD_PARENT_JOT']]);
+                    $aType = $this->_oModule->_oDb->getLotType($iMainLotId);
+                    $this->addMarkers(array('id' => $iMainLotId, 'type' => $aType['type'], 'jot' => (int)$aLotInfo[$CNF['FIELD_PARENT_JOT']]));
+                    return true;
+                }
+
+                return false;
+            case 'list':
+                if (!$this->_isBlockVersion)
+                    return false;
+
+                if (!($iGroupId = $this->_oModule->_oDb->getGroupIdByLotId($this->_iContentId)))
+                    return false;
+
+                $aGroupsList = $this->_oModule->_oDb->getTalksByGroupId($iGroupId);
+                unset($aGroupsList[$this->_iContentId]);
+                if (empty($aGroupsList))
+                    return false;
+
+                /*
+                 *  if (!($this->_isBlockVersion && $aLotInfo[$CNF['FIELD_CLASS']] !== 'members'))
+                    return false;
+                 * */
+
+            case 'mute':
+            case 'star':
+                $this->addMarkers(array('id' => $this->_iContentId));
+                return true;
+            case 'video_call':
+                if (empty($aLotInfo) || $oModule->_oConfig->isJitsiAllowed($aLotInfo[$CNF['FIELD_TYPE']])) {
+                    if (!empty($aLotInfo)) {
+                        $aJVC = $oModule->_oDb->getJVC($this->_iContentId);
+                        $sRoom = empty($aJVC) ? $oModule->_oConfig->getRoomId($aLotInfo[$CNF['FIELD_ID']], $aLotInfo[$CNF['FIELD_AUTHOR']]) : $aJVC[$CNF['FJVC_ROOM']];
+                    } else
+                        $sRoom = $oModule->_oConfig->getRoomId();
+
+                    $this->addMarkers(array('room' => $sRoom, 'id' => $this->_iContentId));
+                    return true;
+                }
+        }
+
+        return false;
+    }
+
+}
+
+/** @} */

+ 4618 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerModule.php

@@ -0,0 +1,4618 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup	Messenger Messenger
+ * @ingroup		UnaModules
+ *
+ * @{
+ */
+
+require_once('BxMessengerStorage.php');
+
+define('BX_IM_TYPE_PUBLIC', 1);
+define('BX_IM_TYPE_PRIVATE', 2);
+define('BX_IM_TYPE_SETS', 3);
+define('BX_IM_TYPE_GROUPS', 4);
+define('BX_IM_TYPE_EVENTS', 5);
+define('BX_IM_TYPE_BROADCAST', 6);
+define('BX_IM_EMPTY_URL', '');
+define('BX_IM_EMPTY', 0);
+
+// Attachment types
+define('BX_ATT_TYPE_FILES', 'files');
+define('BX_ATT_TYPE_FILES_UPLOADING', 'uploading');
+define('BX_ATT_TYPE_GIPHY', 'giphy');
+define('BX_ATT_TYPE_REPOST', 'repost');
+define('BX_ATT_TYPE_CUSTOM', 'custom');
+define('BX_ATT_TYPE_REPLY', 'reply');
+define('BX_ATT_TYPE_VC', 'vc'); // video conference
+define('BX_ATT_GROUPS_ATTACH', 'attachment');
+
+// Reactions actions
+define('BX_JOT_REACTION_ADD', 'add');
+define('BX_JOT_REACTION_REMOVE', 'remove');
+
+// Public jitsi actions
+define('BX_JOT_PUBLIC_JITSI_LEAVE', 'leave');
+define('BX_JOT_PUBLIC_JITSI_JOIN', 'join');
+define('BX_JOT_PUBLIC_JITSI_CLOSE', 'close');
+
+// Notifications types
+define('BX_MSG_NTFS_MESSAGE', 'message');
+define('BX_MSG_NTFS_MENTION', 'mention');
+define('BX_MSG_NTFS_BROADCAST', 'broadcast');
+
+// Membership actions
+define('BX_MSG_ACTION_EDIT_MESSAGES', 'edit_messages');
+define('BX_MSG_ACTION_DELETE_MESSAGES', 'delete_messages');
+define('BX_MSG_ACTION_CREATE_TALKS', 'create_talks');
+define('BX_MSG_ACTION_ADMINISTRATE_TALKS', 'administrate_talks');
+define('BX_MSG_ACTION_SEND_MESSAGE', 'send_messages');
+define('BX_MSG_ACTION_SEND_FIELS', 'send_files');
+define('BX_MSG_ACTION_CREATE_VC', 'create_vc');
+define('BX_MSG_ACTION_CREATE_IM_VC', 'video_conference');
+define('BX_MSG_ACTION_VIDEO_RECORDER', 'video_recorder');
+define('BX_MSG_ACTION_JOIN_IM_VC', 'join_personal_vc');
+define('BX_MSG_ACTION_JOIN_TALK_VC', 'join_vc');
+define('BX_MSG_ACTION_CREATE_GROUPS', 'create_groups');
+define('BX_MSG_ACTION_CREATE_BROADCASTS', 'create_broadcasts');
+
+// Lot settings
+define('BX_MSG_SETTING_MSG', 'msg'); // allow to send messages
+define('BX_MSG_SETTING_GIPHY', 'giphy'); // allow to send giphy
+define('BX_MSG_SETTING_FILES', 'files'); // allow to send files
+define('BX_MSG_SETTING_VIDEO_RECORD', 'video_rec'); // allow to record videos
+define('BX_MSG_SETTING_SMILES', 'smiles'); // allow to record videos
+
+// Talk's types
+define('BX_MSG_TALK_TYPE_INBOX', 'inbox');
+define('BX_MSG_TALK_TYPE_THREADS', 'threads');
+define('BX_MSG_TALK_TYPE_GROUPS', 'groups');
+define('BX_MSG_TALK_TYPE_DIRECT', 'direct');
+define('BX_MSG_TALK_TYPE_FILES', 'files');
+define('BX_MSG_TALK_TYPE_MR', 'mr');
+define('BX_MSG_TALK_TYPE_REPLIES', 'replies');
+define('BX_MSG_TALK_TYPE_SAVED', 'saved');
+define('BX_MSG_TALK_TYPE_FAVORITES', 'favorites');
+define('BX_MSG_TALK_TYPE_BROADCAST', 'broadcast');
+
+// Pages types
+define('BX_MSG_TALK_TYPE_PAGES', 'bx_messenger_pages');
+
+// Search criteria
+define('BX_SEARCH_CRITERIA_TITLES', 'titles');
+define('BX_SEARCH_CRITERIA_PARTS', 'participants');
+define('BX_SEARCH_CRITERIA_CONTENT', 'content');
+
+// Visibility
+define('BX_MSG_VISIBILITY_VISIBLE', 0);
+define('BX_MSG_VISIBILITY_HIDDEN', 1);
+define('BX_MSG_VISIBILITY_ALL', 2);
+
+// Talks classes
+define('BX_MSG_TALK_CLASS_MEMBERS', 'members');
+define('BX_MSG_TALK_CLASS_MARKET', 'market');
+define('BX_MSG_TALK_CLASS_CUSTOM', 'custom');
+define('BX_MSG_TALK_CLASS_BLOCK', 'block');
+
+// Classes
+define('BX_MSG_CLASS_ATTACHMENT', 'message-attachment');
+
+/**
+ * Messenger module
+ */
+class BxMessengerModule extends BxBaseModGeneralModule
+{
+    const BROADCAST_BATCH_SIZE = 200;
+    const BROADCAST_BATCHES_PER_RUN = 5;
+
+    private $_iUserId = 0;
+    private $_iJotId = 0;
+    private $_iSelectedProfileId = 0;
+    private $_iSelectedConvoId = 0;
+    private $_isBlockMessenger = false;
+    private $_sWelcomeMessage = '';
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+        $this->_iUserId = bx_get_logged_profile_id();
+    }
+
+    public function setProfileId($iProfileId)
+    {
+        $iPrev = (int)$this->_iProfileId;
+        $this->_iProfileId = (int)$iProfileId;
+        $this->_iUserId = (int)$iProfileId;
+        return $iPrev;
+    }
+
+    /**
+     * Returns left side block for messenger page and loads config data
+     */
+    public function serviceGetBlockInbox()
+    {
+        $mixedContent = $this->_oTemplate->getLotsList($this->_iProfileId, $this->_iSelectedConvoId);
+        if (bx_is_api())
+            return $mixedContent;
+
+        return $mixedContent . $this->_oTemplate->loadConfig(
+            $this->_iProfileId,
+            [
+                'is_block_version' => false,
+                'lot' => $this->_iSelectedConvoId,
+                'jot' => $this->_iJotId,
+                'selected_profile' => $this->_iSelectedProfileId,
+                'welcome' => $this->_sWelcomeMessage
+            ]
+        );
+    }
+    /**
+     * Returns right side block for messenger page
+     */
+    public function serviceGetBlockLot()
+    {
+        if ($this->_iSelectedConvoId) {
+            return $this->_oTemplate->getTalkBlock($this->_iProfileId, $this->_iSelectedConvoId, $this->_iJotId);
+        } else if ($this->_iSelectedProfileId && $this->onCheckContact($this->_iProfileId, $this->_iSelectedProfileId))
+            return $this->_oTemplate->getTalkBlockByUserName($this->_iProfileId, $this->_iSelectedProfileId);
+
+        return $this->_oTemplate->getTalkBlock($this->_iProfileId, BX_IM_EMPTY);
+    }
+
+    /**
+     * Returns block with messenger for any page
+     * @param string $sModule module name
+     * @return string block's content
+     */
+    public function serviceGetBlockMessenger($sModule, $bAsHtml = false)
+    {
+        $this->_isBlockMessenger = true;
+        $CNF = &$this->_oConfig->CNF;
+
+        $this->_oTemplate->loadCssJs('view');
+
+        $sClass = str_replace('{type}', '', $sModule);
+        $aLotInfo = [];
+        if ($sClass)
+            $aLotInfo = $this->_oDb->getLotByClass($sClass);
+
+        $sUrl = $this->_oConfig->getPageIdent();
+
+        $iType = $this->_oConfig->getTalkType($sModule);
+        if (empty($aLotInfo) && $sUrl)
+            $aLotInfo = $this->_oDb->findLotByParams(array(
+                $CNF['FIELD_TYPE'] => $iType,
+                $CNF['FIELD_CLASS'] => BX_MSG_TALK_CLASS_CUSTOM,
+                $CNF['FIELD_URL'] => $sUrl
+            ));
+
+        $iLotId = !empty($aLotInfo) && isset($aLotInfo[$CNF['FIELD_ID']]) ? (int) $aLotInfo[$CNF['FIELD_ID']] : 0;
+        if (!$iLotId) {
+
+            if (!$sUrl)
+                return false;
+
+            $sTalkUrl = $this->getPreparedUrl($sUrl);
+            $sTalkTitle = BxDolTemplate::getInstance()->getPageHeader();
+
+            $iLotId = $this->_oDb->createLot(
+                $this->_iProfileId,
+                array(
+                    'url' => $sTalkUrl,
+                    'title' => $sTalkTitle,
+                    'type' => $iType,
+                    'page' => $this->_oConfig->getPageName($sUrl)
+                )
+            );
+
+            $this->_oDb->registerGroup($sUrl, $iLotId);
+        }
+
+        $sConfig = $this->_oTemplate->loadConfig($this->_iProfileId, ['lot' => $iLotId, 'type' => $iType]);
+        $aHeader = $this->_oTemplate->getTalkHeader($iLotId, $this->_iProfileId, true, true);
+
+        $sContent = $this->_oTemplate->parseHtmlByName('talk-body.html', array(
+            'history' => $this->_oTemplate->getHistory($this->_iProfileId, $iLotId, BX_IM_EMPTY),
+            'text_area' => $this->_oTemplate->getTextArea($this->_iProfileId, $iLotId),
+            'groups_list' => $this->_oTemplate->getTalksList($iLotId),
+            'info' => $this->_oTemplate->getInfoSection($iLotId, $this->_iProfileId),
+        ));
+
+        $this->_oTemplate->addCss(array('threads.css'));
+        if ($bAsHtml) {
+            $sContent = $this->_oTemplate->getTalkBlock($this->_iProfileId, $iLotId, BX_IM_EMPTY, $this->_isBlockMessenger);
+            return $sConfig . $sContent;
+        }
+
+        $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_TALK_MENU']);
+        $oMenu->setContentId($iLotId);
+
+        return array(
+            'title' => $aHeader['title'],
+            'content' => $sConfig . $sContent,
+            'menu' => $oMenu
+        );
+    }
+
+    private function initDefaultParams()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $this->_iSelectedProfileId = (int) bx_get('profile_id');
+        if ($this->_iSelectedProfileId && !$this->onCheckContact($this->_iProfileId, $this->_iSelectedProfileId))
+            $this->_iSelectedProfileId = 0;
+
+        if ($this->_iSelectedProfileId) {
+            $oProfile = BxDolProfile::getInstance($this->_iSelectedProfileId);
+            $sModule = $oProfile->getModule();
+            $bIsProfile = BxDolRequest::serviceExists($sModule, 'act_as_profile') && BxDolService::call($sModule, 'act_as_profile');
+            if ($sModule && !$bIsProfile && BxDolRequest::serviceExists($sModule, 'is_group_profile') && BxDolService::call($sModule, 'is_group_profile')) {
+                $aOwnerInfo = BxDolService::call($sModule, 'get_info', array($oProfile->getContentId(), false));
+                if (!empty($aOwnerInfo) && is_array($aOwnerInfo) && BxDolService::call($sModule, 'check_allowed_view_for_profile', array($aOwnerInfo)) === CHECK_ACTION_RESULT_ALLOWED) {
+                    $oModule = BxDolModule::getInstance($sModule);
+                    if ($oModule->_oConfig) {
+                        $oMCNF = $oModule->_oConfig->CNF;
+                        $sUrl = "i={$oMCNF['URI_VIEW_ENTRY']}&id=" . $oProfile->getContentId();
+                        if ($sUrl && $aTalk = $this->_oDb->getLotByUrl($sUrl))
+                            $this->_iSelectedConvoId = $aTalk[$CNF['FIELD_ID']];
+                    }
+                }
+            } else {
+                $aExistedTalk = $this->_oDb->getLotsByParticipantsList([$this->_iSelectedProfileId, $this->_iProfileId], BX_IM_TYPE_PRIVATE);
+                if (!empty($aExistedTalk))
+                    $this->_iSelectedConvoId = $aExistedTalk[$CNF['FIELD_ID']];
+            }
+        } else {
+            if ($this->_iJotId)
+                $this->_iSelectedConvoId = $this->_oDb->getLotByJotId($this->_iJotId);
+            else {
+                $aLotsList = $this->_oDb->getMyLots($this->_iProfileId, ['inbox' => true]);
+                $this->_iSelectedConvoId = !empty($aLotsList) ? current($aLotsList)[$CNF['FIELD_ID']] : 0;
+            }
+        }
+    }
+
+    public function serviceGetMainMessengerPage()
+    {
+        if (bx_is_api())
+            return bx_srv($this->getName(), 'get_block_main', [], 'Services');
+
+        $this->initDefaultParams();
+
+        $bSimpleMode = $this->_oConfig->CNF['USE-UNIQUE-MODE'];
+        $aData = [
+            'menu' => $this->_oTemplate->getLeftMainMenu(),
+            'info' => $this->_oTemplate->getInfoSection(),
+            'list' => $this->serviceGetBlockInbox(),
+            'history' => $this->serviceGetBlockLot(),
+            'list_width' => $bSimpleMode ? 'xl:col-span-3' : 'xl:col-span-3',
+            'history_width' => $bSimpleMode ? 'xl:col-span-7' : 'xl:col-span-5',
+        ];
+
+        return $this->_oTemplate->parseHtmlByName('main.html', $aData);
+    }
+
+    public function actionGetThreadReplies()
+    {
+        $iJotId = (int) bx_get('jot_id');
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+        $aLotInfo = $this->_oDb->getLotByParentId($iJotId);
+        $CNF = &$this->_oConfig->CNF;
+
+        if (empty($aJotInfo) || !$this->isLogged() || ($aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_PRIVATE && !$this->_oDb->isParticipant($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $this->_iProfileId)))
+            return echoJson(array('code' => 1));
+
+        echoJson(array('code' => 0, 'html' => $this->_oTemplate->getThreadReply($iJotId)));
+    }
+
+    public function actionGetThreadsPanel()
+    {
+        $iJotId = bx_get('jot_id');
+        if (!$this->isLogged() || !$iJotId)
+            return echoJson(array('code' => 1));
+
+        $CNF = $this->_oConfig->CNF;
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+        if (empty($aJotInfo))
+            return echoJson(array('code' => 1));
+
+        $aLotInfo = $this->_oDb->getLotByParentId($iJotId);
+        if (!empty($aLotInfo) && $aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_PRIVATE && !$this->_oDb->isParticipant($aLotInfo[$CNF['FIELD_ID']], $this->_iProfileId))
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_not_participant')));
+
+        echoJson(array('code' => 0, 'html' => $this->_oTemplate->getThreadsPanel($iJotId, $this->_iProfileId)));
+    }
+    /**
+     * Adds messenger block to all pages with comments and trigger pages during installation
+     */
+    public function serviceAddMessengerBlocks()
+    {
+        if (!isAdmin())
+            return '';
+
+        $aPages = $this->_oDb->getPagesWithComments();
+
+        $aUrl = array();
+        foreach ($aPages as $sModule => $sPage) {
+            $sParams = parse_url($sPage, PHP_URL_QUERY);
+            if (!empty($sParams))
+                parse_str($sParams, $aUrl);
+
+            if (isset($aUrl['i'])) {
+                $sPage = BxDolPageQuery::getPageObjectNameByURI($aUrl['i']);
+                if (!$this->_oDb->isBlockAdded($sPage))
+                    $this->_oDb->addMessengerBlock($sPage);
+            }
+        }
+    }
+
+    /**
+     * Builds main messenger page
+     */
+    public function actionHome()
+    {
+        if (!$this->isLogged())
+            bx_login_form();
+
+        $oPage = BxDolPage::getObjectInstance('bx_messenger_main');
+        if (!$oPage) {
+            $this->_oTemplate->displayPageNotFound();
+            exit;
+        }
+
+        $oPage->displayPage();
+    }
+
+    public function actionArchive($iJotId)
+    {
+        $iLotId = $this->_oDb->getLotByJotId($iJotId);
+        $this->_iJotId = $iJotId;
+        if (!$iLotId) {
+            $this->_sWelcomeMessage = _t('_bx_messenger_not_found_to_read');
+            $this->_iJotId = BX_IM_EMPTY;
+        } else
+            if (!$this->_oDb->isParticipant($iLotId, $this->_iProfileId)) {
+                $this->_sWelcomeMessage = _t('_bx_messenger_not_allowed_to_read');
+                $this->_iJotId = BX_IM_EMPTY;
+            }
+
+        $this->actionHome();
+    }
+
+    /**
+     * Create List of participants received from request (POST, GET)
+     * @param mixed $mixedParticipants participants list
+     * @param bool $bExcludeLogged don't add logged profile to the participants list
+     * @return array  participants list
+     */
+    private function getParticipantsList($mixedParticipants, $bExcludeLogged = false)
+    {
+        if (empty($mixedParticipants))
+            return [];
+
+        $aParticipants = is_array($mixedParticipants) ? $mixedParticipants : array(intval($mixedParticipants));
+        if (!$bExcludeLogged && !in_array($this->_iProfileId, $aParticipants))
+            $aParticipants[] = $this->_iProfileId;
+
+        return array_unique($aParticipants, SORT_NUMERIC);
+    }
+
+
+    public function serviceSendMessage($iRecipient = 0, $mixedData = '', $iSender = 0)
+    {
+        if (!$iSender)
+            $iSender = $this->_iProfileId;
+
+        if (empty($mixedData))
+            return _t('_bx_messenger_send_message_no_data');
+
+        $aData = array();
+        if (is_array($mixedData))
+            $aData = $mixedData;
+        elseif (is_string($mixedData) && strlen($mixedData))
+            $aData['message'] = $mixedData;
+
+        if (empty($aData))
+            return _t('_bx_messenger_send_message_no_data');
+
+        $aData['participants'] = array();
+        if ($iRecipient)
+            $aData['participants'][] = $iRecipient;
+
+        if ($iSender)
+            $aData['participants'][] = $iSender;
+
+        $mixedResult = $this->sendMessage($aData, $iRecipient, $iSender);
+        if (is_array($mixedResult) && isset($mixedResult['jot_id']))
+            return true;
+
+        return is_string($mixedResult) ? $mixedResult : false;
+    }
+
+    /**
+     * Main function to send Messages
+     * @param array $aData params for a message, files, text, participants...
+     * @param int $iRecipient recipient's profile id
+     * @param int $iSender sender's profile id
+     * @return array/string
+     */
+    public function sendMessage(&$aData, $iRecipient = 0, $iSender = 0)
+    {
+        $sMessage = isset($aData['message']) ? trim($aData['message']) : '';
+        $iLotId = isset($aData['lot']) ? (int) $aData['lot'] : 0;
+        $iGroupId = isset($aData['group_id']) ? (int) $aData['group_id'] : 0;
+        $iType = (isset($aData['type']) && $aData['type'] && $this->_oDb->isLotType($aData['type'])) ? (int) $aData['type'] : BX_IM_TYPE_PRIVATE;
+        $aFiles = $aData[BX_ATT_TYPE_FILES] ?? [];
+        $aGiphy = $aData[BX_ATT_TYPE_GIPHY] ?? [];
+        $iReply = isset($aData['reply']) ? (int) $aData['reply'] : '';
+        $aParticipants = $aData['participants'] ?? [];
+        $aAttachments = $aData['attachment'] ?? [];
+        $sClass = $aData['class'] ?? '';
+        $sTitle = isset($aData['title']) ? html2txt($aData['title']) : '';
+        $iParent = isset($aData['parent']) ? (int) $aData['parent'] : 0;
+        $sUrl = '';
+
+        $CNF = &$this->_oConfig->CNF;
+
+        // check if message contains toxic
+        if ($sMessage && $CNF['CHECK-CONTENT-FOR-TOXIC'] && BxDolRequest::serviceExists('bx_antispam', 'is_toxic')) {
+            if (bx_srv('bx_antispam', 'is_toxic', [$sMessage]) === true)
+                return _t('_bx_messenger_toxic_message');
+        }
+
+        if ($iRecipient && !($oRecipient = BxDolProfile::getInstance($iRecipient)))
+            return _t('_bx_messenger_profile_not_found');
+
+        if (!$iSender)
+            $iSender = $this->_iProfileId;
+
+        $mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_SEND_MESSAGE, $iSender);
+        if ($mixedResult !== true)
+            return $mixedResult;
+
+        if ($iLotId) {
+            if (!($aLotInfo = $this->_oDb->getLotInfoById($iLotId)))
+                return _t('_bx_messenger_not_found');
+
+            $mixedOptions = $this->_oDb->getLotSettings($iLotId);
+
+            $bCheckAction = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $iSender) === true;
+            $bIsAuthor = $bCheckAction || $this->_oDb->isAuthor($iLotId, $iSender);
+
+            if ($sMessage && $mixedOptions !== false && !in_array(BX_MSG_SETTING_MSG, $mixedOptions) && !$bIsAuthor)
+                return _t('_bx_messenger_send_message_save_error');
+
+            if (!empty($aFiles)) {
+                if (count($aFiles) == 1 && isset(current($aFiles)['content_type']) && current($aFiles)['content_type'] == BX_MSG_SETTING_VIDEO_RECORD) {
+                    if ($mixedOptions !== false && !in_array(BX_MSG_SETTING_VIDEO_RECORD, $mixedOptions) && !$bIsAuthor)
+                        return _t('_bx_messenger_send_message_save_error');
+                }
+                if ($mixedOptions !== false && !in_array(BX_MSG_SETTING_FILES, $mixedOptions) && !$bIsAuthor)
+                    return _t('_bx_messenger_send_message_save_error');
+            }
+
+            if (!empty($aGiphy) && $mixedOptions !== false && !in_array(BX_MSG_SETTING_GIPHY, $mixedOptions) && !$bIsAuthor)
+                return _t('_bx_messenger_send_message_save_error');
+
+            $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+            $iType = isset($aLotInfo[$CNF['FIELD_TYPE']]) ? $aLotInfo[$CNF['FIELD_TYPE']] : BX_IM_TYPE_PUBLIC;
+            if ($iType == BX_IM_TYPE_PRIVATE && !$iRecipient) {
+                $aPartList = $this->_oDb->getParticipantsList($iLotId, true, $iSender);
+                if (count($aPartList) == 1) {
+                    $iRecipient = current($aPartList);
+                }
+            }
+        }
+
+        if ($iRecipient && !$this->onCheckContact($iSender, $iRecipient))
+            return _t('_bx_messenger_contact_privacy_not_allowed');
+
+        if ($iType !== BX_IM_TYPE_PRIVATE || $sClass)
+            $sUrl = isset($aData['url']) ? $aData['url'] : '';
+
+        // prepare participants list
+        $aParticipants = $this->getParticipantsList($aParticipants, $iSender !== $this->_iProfileId);
+        if (!$iLotId && empty($aParticipants) && $iType === BX_IM_TYPE_PRIVATE)
+            return _t('_bx_messenger_save_part_failed');
+
+        $aBroadcastParticipants = null;
+        if ($iType === BX_IM_TYPE_BROADCAST && isset($aData[BX_MSG_TALK_TYPE_BROADCAST])) {
+            $aBroadcastParticipants = $this->getProfilesByCriteria($aData[BX_MSG_TALK_TYPE_BROADCAST], [
+                'include_profiles' => $aParticipants
+            ]);
+            if (empty($aBroadcastParticipants))
+                return _t('_bx_messenger_save_part_failed');
+        }
+
+        $sMessage = $this->prepareMessageToDb($sMessage);
+        if ($sMessage && $iType != BX_IM_TYPE_PRIVATE && $sUrl)
+            $sUrl = $this->getPreparedUrl($sUrl);
+
+        if ($sClass === BX_MSG_TALK_CLASS_MARKET)
+            $sUrl = $this->_oConfig->getPageIdent($sUrl);
+
+        $aResult = [];
+        if (
+            ($sMessage || !empty($aFiles) || !empty($aGiphy) || !empty($aAttachments)) && ($iId = $this->_oDb->saveMessage(
+                array(
+                    'message' => $sMessage,
+                    'type' => $iType,
+                    'member_id' => $this->_iProfileId,
+                    'url' => $sUrl,
+                    'title' => $sTitle,
+                    'lot' => $iLotId,
+                    'reply' => $iReply,
+                    'parent' => $iParent,
+                    'group_id' => $iGroupId,
+                    'class' => $sClass,
+                    'participants' => $aParticipants
+                )
+            ))
+        ) {
+            if (!$iLotId)
+                $aResult['lot_id'] = $this->_oDb->getLotByJotId($iId);
+
+            $aBroadcastContext = [];
+            if ($iType === BX_IM_TYPE_BROADCAST && !$iLotId && (int) $aResult['lot_id']) {
+                $iAuthorId = $this->_iProfileId;
+                $iCreatedLot = (int) $aResult['lot_id'];
+                if (isset($aData[BX_MSG_TALK_TYPE_BROADCAST]['author']) && (int) $aData[BX_MSG_TALK_TYPE_BROADCAST]['author'])
+                    $iAuthorId = $aData[BX_MSG_TALK_TYPE_BROADCAST]['author'];
+
+                if ($aBroadcastParticipants === null)
+                    $aBroadcastParticipants = $this->getProfilesByCriteria($aData[BX_MSG_TALK_TYPE_BROADCAST], [
+                        'include_profiles' => $aParticipants
+                    ]);
+                if (!is_array($aBroadcastParticipants))
+                    $aBroadcastParticipants = [];
+
+                $aPartList = array_unique(array_merge($aParticipants, (array) $aBroadcastParticipants), SORT_NUMERIC);
+
+                $sModule = $this->_oConfig->getObject('alert');
+                $sTemplateName = $this->_oConfig->getName();
+                bx_alert($sModule, 'broadcast_attachment_before', $iId, $aResult['lot_id'], [
+                    'id' => &$iId,
+                    'talk_id' => $aResult['lot_id'],
+                    'author' => &$iAuthorId,
+                    'template' => &$sTemplateName,
+                    'participants' => &$aPartList,
+                    'data' => &$aData
+                ]);
+
+                $aAttachments[$sTemplateName] = (int) $iId;
+                $aNotificationsType = [];
+                if (isset($aData[BX_MSG_TALK_TYPE_BROADCAST]['notify_by'])) {
+                    if ($aNotifData = $aData[BX_MSG_TALK_TYPE_BROADCAST]['notify_by'])
+                        $aNotificationsType['silent_mode'] = $this->_oConfig->getSelectedNotificationMode($aNotifData);
+                }
+
+                $aBroadcastContext = [
+                    'author_id' => (int) $iAuthorId,
+                    'lot_id' => $iCreatedLot,
+                    'jot_id' => (int) $iId,
+                    'participants' => array_values(array_unique(array_filter(array_map('intval', $aPartList), function ($iPart) {
+                        return $iPart > 0;
+                    }), SORT_NUMERIC)),
+                    'notifications' => $aNotificationsType
+                ];
+            }
+
+            if (!empty($aFiles)) {
+                $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+                $aUploadingFilesNames = $aCompleteFilesNames = array();
+
+                if (bx_is_api()) {
+                    if (is_array($aFiles))
+                        foreach ($aFiles as &$iFileId) {
+                            if ($aFile = $oStorage->getFile($iFileId)) {
+                                $aCompleteFilesNames[] = $aFile[$CNF['FIELD_ST_NAME']];
+                                $this->_oDb->updateFiles($iFileId, array(
+                                    $CNF['FIELD_ST_JOT'] => $iId,
+                                ));
+                                $oStorage->afterUploadCleanup($iFileId, $this->_iProfileId);
+                            }
+                        }
+                } else
+                    foreach ($aFiles as &$aFile) {
+                        if (!(int) $aFile['complete']) {
+                            $aUploadingFilesNames[] = $aFile['realname'];
+                            continue;
+                        }
+
+                        if (!$this->_oConfig->isValidToUpload($aFile['name'])) {
+                            $this->_oDb->deleteJot($iId, $this->_iProfileId, true);
+                            return _t('_bx_messenger_bad_request');
+                        }
+
+                        $sFile = BX_DIRECTORY_PATH_TMP . basename($aFile['name']);
+                        $sExt = $oStorage->getFileExt($sFile);
+                        $sFilename = $oStorage->getFileTitle($aFile['realname']) . ".{$sExt}";
+                        $iFile = $oStorage->storeFileFromPath($sFile, $iType == BX_IM_TYPE_PRIVATE, $this->_iProfileId, (int) $iId);
+                        if ($iFile) {
+                            $oStorage->afterUploadCleanup($iFile, $this->_iProfileId);
+                            $this->_oDb->updateFiles($iFile, array(
+                                $CNF['FIELD_ST_JOT'] => $iId,
+                                $CNF['FIELD_ST_NAME'] => $sFilename
+                            ));
+                            $aCompleteFilesNames[] = $sFilename;
+                            @unlink($sFile);
+                        }
+                    }
+
+                if (!empty($aUploadingFilesNames))
+                    $aAttachments[BX_ATT_TYPE_FILES_UPLOADING] = $aUploadingFilesNames;
+
+                if (!empty($aCompleteFilesNames))
+                    $aAttachments[BX_ATT_TYPE_FILES] = $aCompleteFilesNames;
+            }
+
+            if (is_array($aGiphy) && !empty($aGiphy))
+                $aAttachments[BX_ATT_TYPE_GIPHY] = current($aGiphy);
+
+            if ($iReply)
+                $this->_oDb->updateJot($iId, $CNF['FIELD_MESSAGE_REPLY'], $iReply);
+
+            if (!empty($aAttachments))
+                $this->_oDb->addAttachment($iId, $aAttachments);
+
+            if (!empty($aBroadcastContext)) {
+                if ($aBroadcastContext['author_id'] && $aBroadcastContext['author_id'] !== (int) $this->_iProfileId) {
+                    $this->_oDb->updateConvoFiled($aBroadcastContext['lot_id'], $CNF['FIELD_AUTHOR'], $aBroadcastContext['author_id']);
+                    $this->_oDb->updateJot($aBroadcastContext['jot_id'], $CNF['FIELD_MESSAGE_AUTHOR'], $aBroadcastContext['author_id']);
+                }
+
+                if (!empty($aBroadcastContext['participants'])) {
+                    $this->_oDb->createBroadcastUsers($aBroadcastContext['lot_id'], $aBroadcastContext['participants']);
+                    if (!$this->queueBroadcastDelivery($aBroadcastContext['lot_id'], $aBroadcastContext['jot_id'], $aBroadcastContext['author_id'], $aBroadcastContext['notifications']))
+                        $this->serviceProcessBroadcastQueue($aBroadcastContext['lot_id'], $aBroadcastContext['jot_id'], $aBroadcastContext['author_id'], $aBroadcastContext['notifications']);
+                }
+            }
+
+            $aResult['jot_id'] = $iId;
+            $aJot = $this->_oDb->getJotById($iId);
+            if (!empty($aJot))
+                $aResult['time'] = bx_time_utc($aJot[$CNF['FIELD_MESSAGE_ADDED']]);
+
+            // Push this jot before onSendJot so a sync agent reply
+            // cannot appear on the socket before the message it is answering.
+            $iBroadcastLot = $iLotId ? (int)$iLotId : (int)($aResult['lot_id'] ?? 0);
+            if (!$iBroadcastLot)
+                $iBroadcastLot = (int)$this->_oDb->getLotByJotId($iId);
+            $this->broadcastNewJot($iBroadcastLot, $iId);
+            $this->onSendJot($iId);
+        } else
+            return _t('_bx_messenger_send_message_save_error');
+
+        BxDolSession::getInstance()->exists();
+        return $aResult;
+    }
+
+    public function actionSend()
+    {
+        $aData = &$_POST;
+        if (!$this->isLogged())
+            return echoJson(['code' => 1, 'message' => _t('_bx_messenger_not_logged'), 'reload' => 1]);
+
+        if (isset($aData[BX_MSG_TALK_TYPE_BROADCAST]) && $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_BROADCASTS) === true)
+            $aData['type'] = BX_IM_TYPE_BROADCAST;
+
+        $aLastJotInfo = isset($aData['lot']) ? $this->_oDb->getLatestJot($aData['lot'], BX_IM_EMPTY, false) : [];
+        $mixedResult = $this->sendMessage($aData);
+        if (is_array($mixedResult)) {
+            if (isset($mixedResult['lot_id']))
+                $mixedResult['header'] = $this->_oTemplate->getTalkHeader($mixedResult['lot_id'], $this->_iProfileId);
+
+            $CNF = &$this->_oConfig->CNF;
+            if (!empty($aLastJotInfo) && isset($aData['tmp_id'])) {
+                $iTimer = $aLastJotInfo[$CNF['FIELD_MESSAGE_ADDED']];
+                $iCurrent = intval($aData['tmp_id'] / 1000);
+                if (($iCurrent - $iTimer) > $CNF['DATE-SHIFT'])
+                    $mixedResult['separator'] = $this->_oTemplate->getDateSeparator($iCurrent);
+            }
+        } else
+            return echoJson(array('code' => 1, 'message' => $mixedResult));
+
+        $mixedResult['code'] = 0;
+        if ($iTmpId = bx_get('tmp_id'))
+            $mixedResult['tmp_id'] = $iTmpId;
+
+        echoJson($mixedResult);
+    }
+
+
+    /**
+     * Loads talk to the right side block when member choose conversation or when open messenger page
+     * @return array with json result
+     */
+    public function actionLoadTalk()
+    {
+        $iLotId = (int) bx_get('lot_id');
+        $iJotId = (int) bx_get('jot_id');
+        $sAreaType = bx_get('area_type');
+        $this->_isBlockMessenger = (bool) bx_get('is_block');
+
+        if (!$iLotId)
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_empty_history'))));
+
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_logged')), 'reload' => 1));
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        $bIsParticipant = $this->_oDb->isParticipant($iLotId, $this->_iProfileId);
+        if (!($bIsParticipant || $this->_oDb->getGroupIdByLotId($iLotId)) && !($aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PUBLIC || $aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_BROADCAST)) {
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_participant')), 'reload' => 1));
+        }
+        ;
+
+        if ((int) bx_get('mark_as_read'))
+            $this->_oDb->readAllMessages($iLotId, $this->_iProfileId);
+
+        $aUnreadJots = $this->_oDb->getNewJots($this->_iProfileId, $iLotId);
+        $iUnreadLotsJots = !empty($aUnreadJots) ? (int) $aUnreadJots[$CNF['FIELD_NEW_UNREAD']] : 0;
+        $iLastUnreadJot = !empty($aUnreadJots) ? (int) $aUnreadJots[$CNF['FIELD_NEW_JOT']] : 0;
+
+        $aHeader = $this->_oTemplate->getTalkHeader($iLotId, $this->_iProfileId, $this->_isBlockMessenger, true);
+        $sHistory = $this->_oTemplate->getHistoryArea(
+            [
+                'profile_id' => $this->_iProfileId,
+                'lot' => $iLotId,
+                'jot' => ($iJotId ? $iJotId : $iLastUnreadJot),
+                'area' => $sAreaType
+            ],
+            $iUnreadLotsJots && $iUnreadLotsJots < ($CNF['MAX_JOTS_BY_DEFAULT'] / 2)
+        );
+        $sTextArea = !in_array($sAreaType, $CNF['VIEW-IN-TALKS']) ? $this->_oTemplate->getTextArea($this->_iProfileId, $iLotId) : '';
+        $aVars = [
+            'code' => 0,
+            'title' => trim(html2txt($aHeader['title'])),
+            'header' => $this->_oTemplate->parseHtmlByName('talk-header.html', $aHeader),
+            'history' => $sHistory,
+            'text_area' => $sTextArea,
+            'last_unread_jot' => $iLastUnreadJot,
+            'unread_jots' => $iUnreadLotsJots,
+            'muted' => (int) $this->_oDb->isMuted($iLotId, $this->_iProfileId),
+            'params' => $this->_oDb->getLotType($iLotId)
+        ];
+
+        if ($this->_isBlockMessenger)
+            $aVars['talks_list'] = $this->_oTemplate->getTalksList($iLotId);
+
+        BxDolSession::getInstance()->exists();
+        echoJson($aVars);
+    }
+
+    public function actionLoadThreadTalk()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_logged')), 'reload' => 1));
+
+        $iJotId = (int) bx_get('jot_id');
+        if (!($aJotInfo = $this->_oDb->getJotById($iJotId)))
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_nothing_found')), 'reload' => 1));
+
+        $aLotInfo = $this->_oDb->getLotByParentId($iJotId);
+        if (empty($aLotInfo)) {
+            $aParentLot = $this->_oDb->getLotInfoById($aJotInfo[$CNF['FIELD_MESSAGE_FK']]);
+            if (empty($aParentLot))
+                return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_nothing_found')), 'reload' => 1));
+
+            if (!$this->_oDb->isParticipant($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $this->_iProfileId)) {
+                return echoJson(array('code' => 1, 'msg' => MsgBox(_t('_bx_messenger_not_participant')), 'reload' => 1));
+            }
+
+            $iLotId = $this->_oDb->createLot(
+                $this->_iProfileId,
+                [
+                    'title' => $aJotInfo[$CNF['FIELD_MESSAGE']] ? strmaxtextlen($aJotInfo[$CNF['FIELD_MESSAGE']]) : $aParentLot[$CNF['FIELD_TITLE']],
+                    'type' => BX_IM_TYPE_PRIVATE,
+                    'thread' => $iJotId,
+                    'participants' => explode(',', $aParentLot[$CNF['FIELD_PARTICIPANTS']])
+                ]
+            );
+
+            $this->onCreateLot($iLotId);
+        } else
+            $iLotId = $aLotInfo[$CNF['FIELD_ID']];
+
+        echoJson(['code' => 0, 'lot' => $iLotId]);
+    }
+
+    public function actionMarkJotsAsRead()
+    {
+        $iId = (int) bx_get('lot');
+        if (!$this->isLogged() || !$this->_oDb->isParticipant($iId, $this->_iUserId)) {
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_logged')), 'reload' => 1));
+        }
+        ;
+
+        $this->_oDb->readAllMessages($iId, $this->_iUserId);
+        echoJson(array('code' => 0));
+    }
+
+    public function actionViewedJot()
+    {
+        $iJotId = (int) bx_get('jot_id');
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+        $aLotInfo = $this->_oDb->getLotByJotId($iJotId, false);
+        $CNF = &$this->_oConfig->CNF;
+
+        if (empty($aJotInfo) || !$this->isLogged() || ($aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_PRIVATE && !$this->_oDb->isParticipant($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $this->_iProfileId)))
+            return echoJson(array('code' => 1));
+
+        $this->_oDb->readMessage($iJotId, $this->_iProfileId);
+        $this->_oDb->markNotificationAsRead($this->_iProfileId, $aJotInfo[$CNF['FIELD_MESSAGE_FK']]);
+        $aUnreadInfo = $this->_oDb->getNewJots($this->_iProfileId, $aJotInfo[$CNF['FIELD_MESSAGE_FK']]);
+        $iUnreadJotsNumber = $iLastUnreadJotId = 0;
+        if (!empty($aUnreadInfo)) {
+            $iLastUnreadJotId = (int) $aUnreadInfo[$CNF['FIELD_NEW_JOT']];
+            $iUnreadJotsNumber = (int) $aUnreadInfo[$CNF['FIELD_NEW_UNREAD']];
+        }
+
+        echoJson(array('code' => 0, 'unread_jots' => $iUnreadJotsNumber, 'last_unread_jot' => $iLastUnreadJotId));
+    }
+
+    /**
+     * Loads messages for specified lot(conversation)
+     * @return array with json
+     */
+    public function actionLoadJots()
+    {
+        $iId = (int) bx_get('id');
+        $iJotId = (int) bx_get('jot_id');
+        if (!$this->isLogged() || !$iId)
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_logged'))));
+
+        if (!$this->_oDb->isParticipant($iId, $this->_iProfileId))
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_participant'))));
+
+        $sContent = $this->_oTemplate->getHistoryArea(['profile_id' => $this->_iProfileId, 'lot' => $iId, 'jot' => $iJotId]);
+        echoJson(array('code' => 0, 'history' => $sContent));
+    }
+
+    /**
+     * Loads search users area
+     * @return array|void
+     */
+    public function actionLoadList()
+    {
+        if (!$this->isLogged())
+            return echoJson(['code' => 1, 'msg' => MsgBox(_t('_bx_messenger_not_logged'))]);
+
+        $sAction = bx_get('action');
+        $iGroupId = (int) bx_get('group_id');
+        $sGroupType = bx_get('area_type');
+        $iLotId = (int) bx_get('lot');
+        if ($sAction === 'edit') {
+            if (!$iLotId || !($bAllowed = $this->_oDb->isAuthor($iLotId, $this->_iProfileId) || $this->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true))
+                return echoJson(['code' => 1]);
+        } else
+            $iLotId = 0;
+
+        $aProfilesList = [];
+        $bIsGroupedChat = $sGroupType === BX_MSG_TALK_TYPE_GROUPS && $iGroupId;
+        if ($bIsGroupedChat) {
+            $aProfilesList = $this->getParticipantsListByGroupAndFilter($iGroupId);
+            if (!empty($aProfilesList))
+                $aProfilesList = array_map(function ($aValue) {
+                    return $aValue['value'];
+                }, $aProfilesList);
+        }
+
+        if (($mixedContent = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_TALKS, $this->_iProfileId)) !== true) {
+            return echoJson([
+                'code' => 1,
+                'msg' => $sContent = MsgBox($mixedContent)
+            ]);
+        }
+
+        $sContent = $this->_oTemplate->getCreateListArea($iLotId, $aProfilesList, $bIsGroupedChat);
+        echoJson([
+            'code' => 0,
+            'content' => $sContent,
+            'text_area' => $this->_oTemplate->getTextArea($this->_iProfileId, $iLotId)
+        ]);
+    }
+
+    private function getParticipantsListByGroupAndFilter($iGroupId, $sTerm = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $mixedUsers = [];
+        if (!$iGroupId || !($aGroupInfo = $this->_oDb->getGroupById($iGroupId)) || $aGroupInfo[$CNF['FMG_MODULE']] === BX_MSG_TALK_TYPE_PAGES)
+            return false;
+
+        $oContextProfile = BxDolProfile::getInstance($aGroupInfo[$CNF['FMG_PROFILE_ID']]);
+        if ($oContextProfile && ($mixedUsers = bx_srv($aGroupInfo[$CNF['FMG_MODULE']], 'fans', array($oContextProfile->getContentId(), true)))) {
+            if ($sTerm)
+                $mixedUsers = array_filter($mixedUsers, function ($iVal) use ($sTerm) {
+                    $oProfile = BxDolProfile::getInstance($iVal);
+                    return stripos($oProfile->getDisplayName(), $sTerm) !== false ? ['value' => $iVal] : false;
+                });
+
+            $mixedUsers = array_map(function ($iVal) {
+                return ['value' => $iVal];
+            }, $mixedUsers);
+        }
+
+        return $mixedUsers;
+    }
+
+    function actionGetUsersList()
+    {
+        $aResult = array('code' => 1);
+        $sTerm = bx_get('term');
+        $iLotId = (int) bx_get('lot');
+        $sAreaType = bx_get('area_type');
+        if (!$this->isLogged())
+            return echoJson($aResult);
+
+        $sExcept = bx_get('except');
+        $aExcept = array();
+        if ($sExcept)
+            $aExcept = explode(',', $sExcept);
+
+        if ($iLotId && !$this->_oDb->isParticipant($iLotId, $this->_iProfileId))
+            return echoJson($aResult);
+
+        $mixedUsers = false;
+        if ($iLotId && $sAreaType === 'groups' && ($iGroupId = $this->_oDb->getGroupIdByLotId($iLotId))) {
+            $mixedUsers = $this->getParticipantsListByGroupAndFilter($iGroupId, $sTerm);
+            if (is_array($mixedUsers))
+                $mixedUsers = array_filter($mixedUsers, function ($aValue) use ($aExcept) {
+                    return !in_array($aValue['value'], $aExcept);
+                });
+        }
+
+        $aUsers = $mixedUsers !== false ? $mixedUsers : $this->searchProfiles($sTerm, $aExcept, $this->_oConfig->CNF['PARAM_SEARCH_DEFAULT_USERS']);
+        if (empty($aUsers))
+            return echoJson(array('content' => MsgBox(_t('_bx_messenger_empty_users_list'))));
+
+        $aProfiles = array();
+        foreach ($aUsers as &$aValue) {
+            if (!$this->onCheckContact($this->_iProfileId, $aValue['value']))
+                continue;
+
+            $aProfiles[] = $aValue['value'];
+        }
+
+        if (!empty($aProfiles))
+            $aResult['content'] = $this->_oTemplate->getProfilesListPreviewForCreateTalkArea($aProfiles);
+
+        echoJson($aResult);
+    }
+
+    /**
+     * Search for Lots by keywords in the right side block
+     * @return string with json
+     */
+    public function actionSearch()
+    {
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_logged')), 'reload' => 1));
+
+        $sParam = bx_get('param');
+        $iStarred = bx_get('starred');
+        $sType = bx_get('type');
+        $iGroupId = bx_get('group_id');
+
+        $aResult = ['code' => 0];
+        $aParams = ['term' => $sParam, 'star' => (bool) $iStarred];
+
+        $aFoundConvos = $aMyLots = [];
+        switch ($sType) {
+            case BX_MSG_TALK_TYPE_THREADS:
+                $aParams['threads'] = true;
+                $aMyLots = $this->_oDb->getMyLots($this->_iProfileId, $aParams, $aFoundConvos);
+                break;
+            case 'public':
+                $aParams['type'] = [BX_IM_TYPE_SETS, BX_IM_TYPE_PUBLIC];
+                $aMyLots = $this->_oDb->getMyLots($this->_iProfileId, $aParams, $aFoundConvos);
+                break;
+            case BX_MSG_TALK_TYPE_MR:
+                $aMyLots = $this->_oDb->getReactionsMentionsLots($this->_iProfileId, $aParams);
+                break;
+            case BX_MSG_TALK_TYPE_REPLIES:
+                $aMyLots = $this->_oDb->getLotsWithReplies($this->_iProfileId, $aParams);
+                break;
+            case BX_MSG_TALK_TYPE_SAVED:
+                $aMyLots = $this->_oDb->getSavedJotInLots($this->_iProfileId, $aParams);
+                break;
+            case BX_MSG_TALK_TYPE_GROUPS:
+                $aParams['group'] = $iGroupId;
+                $aMyLots = $this->_oDb->getMyLots($this->_iProfileId, $aParams, $aFoundConvos);
+                break;
+            case BX_MSG_TALK_TYPE_DIRECT:
+                $aParams['type'] = [BX_IM_TYPE_PRIVATE];
+            default:
+                $aMyLots = $this->_oDb->getMyLots($this->_iProfileId, $aParams, $aFoundConvos);
+        }
+
+        if (empty($aMyLots))
+            $sContent = $sParam ? MsgBox(_t('_bx_messenger_txt_msg_no_results')) : $this->_oTemplate->getFriendsList($sParam); {
+            $sContent = $this->_oTemplate->getLotsPreview($this->_iProfileId, $aMyLots);
+            if (!empty($aFoundConvos['jots_list'])) {
+                foreach ($aFoundConvos['jots_list'] as $iJot => $iConvo) {
+                    $aResult['search_list'][$iConvo]['list'][] = $iJot;
+                }
+            }
+        }
+
+        $aResult['html'] = $sContent;
+        echoJson($aResult);
+    }
+
+    /**
+     * Update brief of the specified lot in the lots list
+     * @return string with json
+     */
+    public function actionUpdateLotBrief()
+    {
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_logged')), 'reload' => 1));
+
+        $iLotId = (int) bx_get('lot_id');
+        if (!$iLotId || !$this->_oDb->isParticipant($iLotId, $this->_iProfileId))
+            return echoJson(array('code' => 1));
+
+        if ($aLots = $this->_oDb->getLotInfoById($iLotId)) {
+            return echoJson(array(
+                'code' => 0,
+                'html' => $this->_oTemplate->getLotsPreview($this->_iProfileId, array($aLots)),
+                'muted' => (int) $this->_oDb->isMuted($iLotId, $this->_iProfileId),
+                'params' => $this->_oDb->getLotType($iLotId)
+            ));
+        }
+
+        echoJson(array('code' => 1));
+    }
+
+    /**
+     * Prepare url for Lot title if it was created on separated page
+     * @param string URL
+     * @return string URL
+     */
+    private function getPreparedUrl($sUrl)
+    {
+        if (!$sUrl)
+            return false;
+
+        $aUrl = parse_url($sUrl);
+        return strtolower($aUrl['path'] . (isset($aUrl['query']) ? '?' . $aUrl['query'] : ''));
+    }
+
+    private function isAvailable($iLotId)
+    {
+        if (!$iLotId)
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        if (!empty($aLotInfo) && !$this->_oDb->isParticipant($iLotId, $this->_iProfileId) && $aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PRIVATE)
+            return false;
+
+        return true;
+    }
+
+    /**
+     * Loads messages for  lot(conversation) (when member wants to view history or get new messages from participants)
+     * @return string with json
+     */
+    public function actionUpdate()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iJot = (int) bx_get('jot');
+        $iLotId = (int) bx_get('lot');
+        $sLoad = bx_get('load');
+        $sArea = bx_get('area_type');
+
+        if (!$this->isLogged() && !($sLoad && $iJot && $iLotId))
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_not_logged'), 'reload' => 1));
+
+        if (!$this->isAvailable($iLotId))
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_talk_is_not_allowed')));
+
+        $sUrl = bx_get('url');
+        if ($sUrl)
+            $sUrl = $this->getPreparedUrl($sUrl);
+
+        $isFocused = (bool) bx_get('focus');
+        $iRequestedJot = (int) bx_get('req_jot');
+        $iLastViewedJot = (int) bx_get('last_viewed_jot');
+        $bUpdateHistory = true;
+        $sContent = '';
+        $iUnreadJotsNumber = 0;
+        $iLastUnreadJotId = 0;
+        $bAttach = true;
+        $bRemoveSeparator = false;
+        switch ($sLoad) {
+            case 'new':
+                if (!$iJot) {
+                    $iJot = $this->_oDb->getFirstUnreadJot($this->_iProfileId, $iLotId);
+                    if ($iJot)
+                        $iJot = $this->_oDb->getPrevJot($iLotId, $iJot);
+                    else {
+                        $aLatestJot = $this->_oDb->getLatestJot($iLotId);
+                        $iJot = !empty($aLatestJot) ? (int) $aLatestJot[$CNF['FIELD_MESSAGE_ID']] : 0;
+                    }
+                }
+
+                if ($iRequestedJot) {
+                    if ($this->_oDb->getJotsNumber($iLotId, $iJot, $iRequestedJot) >= $CNF['MAX_JOTS_LOAD_HISTORY'])
+                        $bUpdateHistory = false;
+                    else if ($isFocused)
+                        $this->_oDb->readMessage($iRequestedJot, $this->_iProfileId);
+                }
+
+                if ($iLastViewedJot && $bUpdateHistory && $isFocused) {
+                    $this->_oDb->readMessage($iLastViewedJot, $this->_iProfileId);
+                }
+
+                $bAttach = $iJot ? $this->_oDb->getJotsNumber($iLotId, $iJot) < $CNF['MAX_JOTS_LOAD_HISTORY'] : false;
+            case 'all':
+            case 'prev':
+                $aOptions = array(
+                    'lot_id' => $iLotId,
+                    'url' => $sUrl,
+                    'start' => $iJot,
+                    'load' => $sLoad,
+                    'limit' => $CNF['MAX_JOTS_LOAD_HISTORY'],
+                    'views' => true,
+                    'dynamic' => true,
+                    'area' => $sArea
+                );
+
+                $iLastUnreadJotId = $iUnreadJotsNumber = 0;
+                $aUnreadInfo = $this->_oDb->getNewJots($this->_iProfileId, $iLotId);
+                if (!empty($aUnreadInfo)) {
+                    $iLastUnreadJotId = (int) $aUnreadInfo[$CNF['FIELD_NEW_JOT']];
+                    $iUnreadJotsNumber = (int) $aUnreadInfo[$CNF['FIELD_NEW_UNREAD']];
+                }
+
+                $sContent = '';
+                if ($bUpdateHistory) {
+                    $aJots = $this->_oTemplate->getJotsOfLot($this->_iProfileId, $aOptions);
+                    $sContent = $aJots['content'];
+
+                    if (isset($aJots['first_jot']) && $iJot && ($aJotInfo = $this->_oDb->getJotById($iJot))) {
+                        $iFDate = strtotime(date("Y-m-d", $aJots['first_jot'][$CNF['FIELD_MESSAGE_ADDED']]));
+                        $iSDate = strtotime(date("Y-m-d", $aJotInfo[$CNF['FIELD_MESSAGE_ADDED']]));
+                        $bRemoveSeparator = +($iFDate == $iSDate);
+                    }
+                }
+
+                break;
+            case 'edit':
+                $aJotInfo = $this->_oDb->getJotById($iJot);
+                $sContent = $aJotInfo[$this->_oConfig->CNF['FIELD_MESSAGE']];
+                break;
+            case 'vc':
+            case 'delete':
+                $sContent = $this->_oTemplate->getMessageIcons($iJot, $sLoad, $this->_oDb->isAuthor($iLotId, $this->_iProfileId) || isAdmin());
+                break;
+            case 'check_viewed':
+                if ($iLotId && $iJot)
+                    $sContent = $this->_oTemplate->getViewedJotProfiles($iJot, $this->_iProfileId);
+                break;
+
+            case 'reaction':
+                if ($iJot) {
+                    $aJotInfo = $this->_oDb->getJotById($iJot);
+                    if (!empty($aJotInfo))
+                        $sContent = $this->_oTemplate->getJotReactions($iJot);
+                }
+                break;
+        }
+
+        $aResult = array(
+            'code' => 0,
+            'html' => $sContent,
+            'unread_jots' => $iUnreadJotsNumber,
+            'last_unread_jot' => $iLastUnreadJotId,
+            'allow_attach' => +$bAttach,
+            'remove_separator' => +$bRemoveSeparator
+        );
+
+        // update session
+        BxDolSession::getInstance()->exists();
+
+        echoJson($aResult);
+    }
+
+    public function searchFriends($sTerm, $aProfilesList = array())
+    {
+        if (!$sTerm || !($aTerms = preg_split("/[\s,]+/", $sTerm)))
+            return array();
+
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_friends');
+        $aProfiles = !empty($aProfilesList) ? $aProfilesList : $oConnection->getConnectedContent($this->_iProfileId, true);
+
+        $aResult = array();
+        if (empty($aProfiles))
+            return $aResult;
+
+        $aModules = BxDolService::call('system', 'get_profiles_modules', array(), 'TemplServiceProfiles');
+        if (empty($aModules))
+            return $aResult;
+
+        $aProfilesModules = array();
+        foreach ($aModules as &$aModule)
+            $aProfilesModules[$aModule['name']] = BxDolModule::getInstance($aModule['name']);
+
+        $aFieldsFilter = array();
+        foreach ($aProfiles as &$iID) {
+            $oProfileInfo = BxDolProfile::getInstance($iID);
+            if (!empty($oProfileInfo)) {
+                $aInfo = $oProfileInfo->getInfo();
+                if (isset($aInfo['type']) && isset($aProfilesModules[$aInfo['type']])) {
+                    $aProfileInfo = $aProfilesModules[$aInfo['type']]->_oDb->getContentInfoByProfileId($iID);
+
+                    if (!isset($aFieldsFilter[$aInfo['type']])) {
+                        $sFields = getParam($aProfilesModules[$aInfo['type']]->_oConfig->CNF['PARAM_SEARCHABLE_FIELDS']);
+                        if ($sFields)
+                            $aFieldsFilter[$aInfo['type']] = explode(',', $sFields);
+                    }
+
+                    if ($aFieldsFilter[$aInfo['type']]) {
+                        $sSearchString = '';
+                        foreach ($aFieldsFilter[$aInfo['type']] as &$sKey)
+                            $sSearchString .= " {$aProfileInfo[$sKey]}";
+
+                        if ($sSearchString) {
+                            $bResult = true;
+                            foreach ($aTerms as &$sValue)
+                                if (stripos($sSearchString, $sValue) === FALSE) {
+                                    $bResult = false;
+                                    break;
+                                }
+
+                            if ($bResult)
+                                $aResult[$aInfo['type']][] = $aProfileInfo;
+                        }
+                    }
+                }
+            }
+        }
+
+        return $aResult;
+    }
+
+    public function searchProfiles($sTerm, $aExcept = array(), $iLimit = 10)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $aModules = BxDolService::call('system', 'get_profiles_modules', array(), 'TemplServiceProfiles');
+        if (empty($aModules))
+            return array();
+
+        if ($CNF['USE-FRIENDS-ONLY-MODE'])
+            $aSearchResult = $this->searchFriends($sTerm);
+        else {
+            $aModules = array_map(function ($aModule) {
+                return $aModule['name'];
+            }, $aModules);
+
+            $o = new BxDolSearch($aModules);
+            $o->setDataProcessing(true);
+            $o->setCustomSearchCondition(array('keyword' => $sTerm));
+            $o->setCustomCurrentCondition(array(
+                'paginate' => array(
+                    'perPage' => $iLimit / count($aModules) + 0.5
+                )
+            ));
+
+            $aSearchResult = $o->response();
+            if (empty($aSearchResult))
+                return array();
+        }
+
+        bx_alert($this->getName(), 'get_profiles_list', 0, 0, ['term' => $sTerm, 'override_list' => &$aSearchResult]);
+
+        $aUsers = [];
+        if (!bx_is_api()) {
+            foreach ($aSearchResult as $sModule => $aItems) {
+                $aCNF = BxDolModule::getInstance($sModule)->_oConfig->CNF;
+                $sTitleField = $aCNF['FIELD_TITLE'];
+                $sIdField = $aCNF['FIELD_ID'];
+                foreach ($aItems as &$aItem) {
+                    $sLabel = isset($aItem[$sTitleField]) ? $aItem[$sTitleField] : false;
+                    if ($sLabel)
+                        $aUsers[] = array(
+                            'value' => BxDolProfile::getInstanceByContentAndType($aItem[$sIdField], $sModule)->id(),
+                            'label' => $sLabel,
+                        );
+                }
+            }
+
+            // sort result
+            usort($aUsers, function ($r1, $r2) {
+                return strcmp($r1['label'], $r2['label']);
+            });
+
+            if (!empty($aExcept))
+                $aUsers = array_filter($aUsers, function ($aUsers) use ($aExcept) {
+                    return !in_array($aUsers['value'], $aExcept);
+                });
+        } else
+            $aUsers = $aSearchResult;
+
+        return array_slice($aUsers, 0, $iLimit);
+    }
+    /**
+     * Occurs when member adds or edit participants list for new of specified lot
+     * @return string with json code
+     */
+    public function actionGetAutoComplete()
+    {
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_not_logged'), 'reload' => 1));
+
+        $aResult = $aExcept = array();
+        $sExcept = bx_get('except');
+        if ($sExcept)
+            $aExcept = explode(',', $sExcept);
+
+        $aUsers = $this->searchProfiles(bx_get('term'), $aExcept, $this->_oConfig->CNF['PARAM_SEARCH_DEFAULT_USERS']);
+        if (empty($aUsers))
+            return echoJson(array('items' => $aUsers));
+
+        $bDonShowDesc = $this->_oConfig->CNF['DONT-SHOW-DESC'];
+        foreach ($aUsers as &$aValue) {
+            if (!$this->onCheckContact($this->_iUserId, $aValue['value']))
+                continue;
+
+            if (!($oProfile = BxDolProfile::getInstance($aValue['value'])))
+                continue;
+
+            $aProfileInfo = $oProfile->getInfo();
+            $aProfileInfoDetails = BxDolService::call($aProfileInfo['type'], 'get_content_info_by_id', array($aProfileInfo['content_id']));
+            $oAccountInfo = BxDolAccount::getInstance($aProfileInfo['account_id']);
+
+            $sThumb = $oProfile->getThumb();
+            $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+            $sDisplayName = $oProfile->getDisplayName();
+            if (!empty($aProfileInfoDetails) && !empty($oAccountInfo)) {
+                $aResult[$aProfileInfo['type']]['results'][] = array(
+                    'value' => $sDisplayName,
+                    'no_desc' => (int) $bDonShowDesc,
+                    'icon' => $bThumb ? $sThumb : '',
+                    'color' => implode(', ', BxDolTemplate::getColorCode($aValue['value'], 1.0)),
+                    'letter' => mb_substr($sDisplayName, 0, 1),
+                    'id' => $oProfile->id(),
+                    'profile_url' => $oProfile->getUrl(),
+                    'description' => !$bDonShowDesc ? _t(
+                        '_bx_messenger_search_desc',
+                        bx_process_output($oAccountInfo->getInfo()['logged'], BX_DATA_DATE_TS),
+                        bx_process_output($aProfileInfoDetails['added'], BX_DATA_DATE_TS)
+                    ) : ''
+                );
+            }
+        }
+
+        foreach ($aResult as $sKey => $aValues) {
+            $aResult[$sKey]['name'] = _t("_{$sKey}");
+        }
+
+        echoJson(array('results' => $aResult));
+    }
+
+    /**
+     * Returns processed videos by received videos ids
+     * @return array with json
+     */
+    public function actionGetProcessedMedia()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $aMedia = bx_get('media');
+        $aResult = array();
+
+        if (empty($aMedia))
+            return echoJson($aResult);
+
+        $oTranscoderAudio = BxDolTranscoderAudio::getObjectInstance($CNF['OBJECT_MP3_TRANSCODER']);
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        if (empty($oStorage))
+            return echoJson($aResult);
+
+        foreach ($aMedia as &$iMedia) {
+            $aFile = $oStorage->getFile($iMedia);
+            if ($oTranscoderAudio->isMimeTypeSupported($aFile['mime_type']) && $oTranscoderAudio->isFileReady($iMedia))
+                $aResult[$iMedia] = $this->_oTemplate->audioPlayer($oTranscoderAudio->getFileUrl($aFile[$CNF['FIELD_ST_ID']]), true);
+
+            if (!strncmp('video/', $aFile['mime_type'], 6))
+                $aResult[$iMedia] = $this->_oTemplate->getVideoFilesToPlay($aFile);
+        }
+
+        echoJson($aResult);
+    }
+
+    /**
+     * Search for lot by participants list and occurs when member edit participants list
+     * @return string with json
+     */
+    public function actionFindLot()
+    {
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_not_logged'), 'reload' => 1));
+
+        $CNF = &$this->_oConfig->CNF;
+        $aParticipants = $this->getParticipantsList(bx_get('participants'));
+        $aResult = array('lot' => 0);
+        if (
+            !empty($aParticipants) && ($aChat = $this->_oDb->findLotByParams(array(
+                $CNF['FIELD_PARTICIPANTS'] => $aParticipants,
+                $CNF['FIELD_TYPE'] => BX_IM_TYPE_PRIVATE,
+                $CNF['FIELD_CLASS'] => BX_MSG_TALK_CLASS_CUSTOM,
+            )))
+        )
+            $aResult['lot'] = $aChat[$this->_oConfig->CNF['FIELD_ID']];
+
+        echoJson($aResult);
+    }
+
+    /**
+     * Updats participants list (occurs when create new lost with specified participants or update already existed list)
+     * @return string with json
+     */
+
+    public function saveParticipantsList($aParticipants, $iLotId = 0, $bIsBlock = 0)
+    {
+        $iLotIdOr = $iLotId;
+
+        $bCheckAction = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true;
+        if ($iLotId && !($this->_oDb->isAuthor($iLotId, $this->_iProfileId) || $bCheckAction))
+            return ['code' => 1, 'message' => _t('_bx_messenger_lot_action_not_allowed')];
+
+        $CNF = &$this->_oConfig->CNF;
+        if (!$iLotId) {
+            $aLot = $this->_oDb->findLotByParams(array(
+                $CNF['FIELD_PARTICIPANTS'] => $aParticipants,
+                $CNF['FIELD_TYPE'] => BX_IM_TYPE_PRIVATE,
+                $CNF['FIELD_CLASS'] => BX_MSG_TALK_CLASS_CUSTOM,
+            ));
+
+            if (!empty($aLot))
+                $iLotId = $aLot[$this->_oConfig->CNF['FIELD_ID']];
+        }
+
+        $oOriginalParts = [];
+        $aResult = ['code' => 0, 'message' => _t('_bx_messenger_save_part_success')];
+        if (!$iLotId) {
+            $iLotId = $this->_oDb->createNewLot($this->_iProfileId, ['title' => '', 'type' => BX_IM_TYPE_PRIVATE, 'url' => BX_IM_EMPTY_URL], $aParticipants);
+            $aResult['lot'] = $iLotId;
+            $this->onCreateLot($iLotId);
+        } else {
+            $oOriginalParts = $this->_oDb->getParticipantsList($iLotId);
+            if ($iLotIdOr == 0 && $iLotId) {
+                $aResult['lot'] = $iLotId;
+            } else {
+                if (!$this->_oDb->saveParticipantsList($iLotId, $aParticipants))
+                    $aResult = ['code' => 2, 'message' => _t('_bx_messenger_lot_parts_error')];
+            }
+        }
+
+        $aRemoveParticipants = array_diff($oOriginalParts, $aParticipants);
+        $aNewParticipants = array_diff($aParticipants, $oOriginalParts);
+
+        foreach ($aNewParticipants as &$iPartId)
+            $this->onAddNewParticipant($iLotId, $iPartId);
+
+        foreach ($aRemoveParticipants as &$iPartId) {
+            $this->_oDb->deleteNewJot($iPartId, $iLotId);
+            $this->onRemoveParticipant($iLotId, $iPartId);
+        }
+
+        return $aResult;
+    }
+
+    public function actionSaveLotsParts()
+    {
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_not_logged'), 'reload' => 1));
+
+        $iLotId = bx_get('lot');
+        $bIsBlockVersion = +bx_get('is_block');
+        $aParticipants = $this->getParticipantsList(bx_get('participants'));
+        $aResult = array('message' => _t('_bx_messenger_save_part_failed'), 'code' => 1);
+        if (empty(bx_get('participants')))
+            return echoJson($aResult);
+
+        $aResult = $this->saveParticipantsList($aParticipants, $iLotId, $bIsBlockVersion);
+        if ($iLotId) {
+            $aHeader = $this->_oTemplate->getTalkHeader($iLotId, $this->_iProfileId, $bIsBlockVersion, true);
+            $aResult['header'] = $aHeader['title'];
+            $aResult['buttons'] = $aHeader['buttons'];
+        }
+
+        echoJson($aResult);
+    }
+
+    /**
+     * Removes specified lot
+     * @return string with json
+     */
+    public function actionDelete()
+    {
+        $iLotId = bx_get('lot');
+        $aResult = array('message' => _t('_bx_messenger_can_not_delete'), 'code' => 1);
+
+        $bAllowed = $this->_oDb->isAuthor($iLotId, $this->_iProfileId) || ($this->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true);
+        if (!$iLotId || !$bAllowed)
+            return echoJson($aResult);
+
+        $CNF = &$this->_oConfig->CNF;
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        if ($this->_oDb->deleteLot($iLotId)) {
+            $aResult = ['code' => 0];
+            $this->onDeleteLot($aLotInfo);
+        }
+
+        echoJson($aResult);
+    }
+
+    /**
+     * Clear specified lot
+     * @return string with json
+     */
+    public function actionClearHistory()
+    {
+        $iLotId = bx_get('lot');
+        $aResult = array('code' => 1, 'message' => _t('_Empty'));
+        $bAllowed = $this->_oDb->isAuthor($iLotId, $this->_iProfileId) || ($this->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true);
+        if (!$iLotId || !$bAllowed) {
+            return echoJson($aResult);
+        }
+
+        if ($this->_oDb->clearLot($iLotId))
+            $aResult = array('code' => 0);
+
+        echoJson($aResult);
+    }
+
+    /**
+     * Removes specified jot
+     * @return string with json
+     */
+    public function actionDeleteJot()
+    {
+        $iJotId = bx_get('jot');
+        $bCompletely = (int) bx_get('completely');
+        $aReturn = $this->serviceDeleteJot($iJotId, $bCompletely);
+        echoJson($aReturn);
+    }
+
+    public function serviceDeleteJot($iJotId, $bCompletely = false)
+    {
+        $aResult = array('code' => 1);
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+        if (empty($aJotInfo))
+            return $aResult;
+
+        $CNF = &$this->_oConfig->CNF;
+        $bIsAllowedToDelete = $this->_oDb->isAllowedToDeleteJot($iJotId, $this->_iProfileId, $aJotInfo[$CNF['FIELD_MESSAGE_AUTHOR']]);
+        if (!$bIsAllowedToDelete)
+            return $aResult;
+
+        $bIsLotAuthor = $this->_oDb->isAuthor($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $this->_iProfileId);
+        $bDelete = $bCompletely || $CNF['REMOVE_MESSAGE_IMMEDIATELY'];
+        if ($this->_oDb->deleteJot($iJotId, $this->_iProfileId, $bDelete)) {
+            if ($bDelete)
+                $this->onDeleteJot($aJotInfo);
+            $aResult = array('code' => 0, 'html' => !$bCompletely ? $this->_oTemplate->getMessageIcons($iJotId, 'delete', $bIsLotAuthor || isAdmin()) : '');
+        }
+
+        return $aResult;
+    }
+
+    /**
+     * React on defined jot
+     * @return string with json
+     */
+    public function actionJotReaction()
+    {
+        $iJotId = bx_get('jot');
+        $aEmoji = bx_get('emoji');
+        $sAction = bx_get('action');
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+
+        $aResult = array('code' => 1);
+        $CNF = &$this->_oConfig->CNF;
+
+        if (empty($aJotInfo))
+            return echoJson($aResult);
+
+        $bIsParticipant = $this->_oDb->isParticipant($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $this->_iProfileId);
+        $aLotInfo = $this->_oDb->getLotInfoById($aJotInfo[$CNF['FIELD_MESSAGE_FK']]);
+        if (!$bIsParticipant) {
+            if ($aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PRIVATE)
+                return echoJson(array('code' => 1, 'msg' => _t('_bx_messenger_not_participant')));
+
+            if (!empty($aLotInfo))
+                $this->_oDb->addMemberToParticipantsList($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $this->_iProfileId);
+        }
+
+        if ($sAction == BX_JOT_REACTION_ADD && $this->_oDb->addJotReaction($iJotId, $this->_iProfileId, $aEmoji)) {
+            $this->onReactJot($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $iJotId, $aLotInfo[$CNF['FIELD_AUTHOR']], $sAction);
+            $aResult = array('code' => 0, 'html' => $this->_oTemplate->getJotReactions($iJotId));
+        }
+
+        echoJson($aResult);
+    }
+
+    public function actionUpdateReaction()
+    {
+        $iJotId = bx_get('jot');
+        $sEmoji = bx_get('emoji_id');
+        $sAction = bx_get('action');
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+
+        $aResult = array('code' => 1);
+        $CNF = &$this->_oConfig->CNF;
+        if (empty($aJotInfo))
+            return echoJson($aResult);
+
+        $bIsParticipant = $this->_oDb->isParticipant($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $this->_iProfileId);
+        $aLotInfo = $this->_oDb->getLotInfoById($aJotInfo[$CNF['FIELD_MESSAGE_FK']]);
+        if (!$bIsParticipant && $sAction == BX_JOT_REACTION_ADD && $aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PRIVATE)
+            return echoJson($aResult);
+
+        if ($this->_oDb->updateReaction($iJotId, $this->_iProfileId, $sEmoji, $sAction)) {
+            $this->onReactJot(
+                $aJotInfo[$CNF['FIELD_MESSAGE_FK']],
+                $iJotId,
+                $aLotInfo[$CNF['FIELD_AUTHOR']],
+                $sAction === BX_JOT_REACTION_ADD ? BX_JOT_REACTION_ADD : BX_JOT_REACTION_REMOVE
+            );
+
+            $aResult = array('code' => 0);
+        }
+
+        echoJson($aResult);
+    }
+
+    /**
+     * Get body of the jot
+     * @return string with json
+     */
+    public function actionViewJot()
+    {
+        $iJotId = bx_get('jot');
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+        if (empty($aJotInfo) || !(isAdmin() || $this->_oDb->isAuthor($aJotInfo[$this->_oConfig->CNF['FIELD_MESSAGE_FK']], $this->_iProfileId)))
+            return echoJson(array('code' => 1));
+
+        $aResult = array('code' => 0, 'html' => $this->_oTemplate->getJotsBody($iJotId));
+        echoJson($aResult);
+    }
+
+    /**
+     * Get body of the jot
+     * @return string with json
+     */
+    public function actionGetJotPreview()
+    {
+        $iJotId = bx_get('jot');
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+        if (empty($aJotInfo) || !(isAdmin() || $this->_oDb->isAuthor($aJotInfo[$this->_oConfig->CNF['FIELD_MESSAGE_FK']], $this->_iProfileId)))
+            return echoJson(array('code' => 1));
+
+        $aResult = array('code' => 0, 'html' => $this->_oTemplate->getReplyPreview($iJotId));
+        echoJson($aResult);
+    }
+
+
+    /**
+     * Jot edit panel
+     * @return string with json
+     */
+    public function actionEditJotForm()
+    {
+        $iJotId = bx_get('jot');
+        $aResult = array('code' => 1);
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+
+        $mixedResult = $this->_oDb->isAllowedToEditJot($iJotId, $this->_iProfileId);
+        if (empty($aJotInfo) || $mixedResult !== true)
+            return echoJson($aResult);
+
+        echoJson(['code' => 0, 'html' => $this->_oTemplate->getEditJotArea($iJotId)]);
+    }
+
+    public function actionEditJot()
+    {
+        $iJotId = bx_get('jot');
+        $aResult = ['code' => 1];
+        $sMessageData = bx_get('message');
+        if (!($aJotInfo = $this->_oDb->getJotById($iJotId)))
+            return echoJson($aResult);
+
+        $sMessage = $this->prepareMessageToDb($sMessageData);
+        $mixedResult = $this->_oDb->isAllowedToEditJot($iJotId, $this->_iProfileId);
+        if ($mixedResult !== true)
+            return echoJson($aResult);
+
+        if ($this->_oDb->editJot($iJotId, $this->_iProfileId, $sMessage)) {
+            $aResult = ['code' => 0, 'html' => $this->_oTemplate->getMessageIcons($iJotId, 'edit')];
+            $this->onUpdateJot($aJotInfo[$this->_oConfig->CNF['FIELD_MESSAGE_FK']], $iJotId, $aJotInfo[$this->_oConfig->CNF['FIELD_MESSAGE_AUTHOR']]);
+        }
+
+        echoJson($aResult);
+    }
+
+    public function actionSaveJotItem()
+    {
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1));
+
+        $iJotId = bx_get('jot');
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+        $aResult = array('code' => 1, 'msg' => _t('_bx_messenger_nothing_found'));
+        if (empty($aJotInfo))
+            return echoJson($aResult);
+
+        $CNF = &$this->_oConfig->CNF;
+        if (!$this->isAvailable($aJotInfo[$CNF['FIELD_MESSAGE_FK']]))
+            return echoJson(array('code' => 1, 'msg' => _t('_bx_messenger_not_participant')));
+
+        $bSaved = $this->_oDb->isJotSaved($iJotId, $this->_iProfileId);
+        $bCode = false;
+        if ($bSaved)
+            $bCode = $this->_oDb->deleteSavedJotItems($iJotId, $this->_iProfileId);
+        else
+            $bCode = $this->_oDb->saveJotItem($iJotId, $this->_iProfileId);
+
+
+        echoJson(array('code' => (int) !$bCode, 'status' => (int) !$bSaved));
+    }
+
+    /**
+     * Removes specified file by id
+     * @return string with json
+     */
+    public function actionDeleteFile()
+    {
+        $iFileId = bx_get('id');
+        $aResult = array('code' => 1, 'message' => _t('_bx_messenger_post_file_not_found'));
+        if (!$iFileId)
+            return echoJson($aResult);
+
+        $CNF = &$this->_oConfig->CNF;
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        $aFile = $oStorage->getFile($iFileId);
+        if (empty($aFile))
+            return echoJson($aResult);
+
+        $bIsAllowedToDelete = $this->_oDb->isAllowedToDeleteJot($aFile[$CNF['FIELD_ST_JOT']], $this->_iProfileId);
+        if (!$bIsAllowedToDelete)
+            return echoJson($aResult);
+
+        $aJotInfo = $this->_oDb->getJotById($aFile[$CNF['FIELD_ST_JOT']]);
+        $this->_oDb->removeFileFromJot($aJotInfo[$CNF['FIELD_MESSAGE_ID']], $iFileId);
+        if ($oStorage->deleteFile($iFileId, $this->_iProfileId)) {
+            $aResult = array('code' => 0);
+
+            $aJotFiles = $this->_oDb->getJotFiles($aFile[$CNF['FIELD_ST_JOT']]);
+            if (count($aJotFiles) == 0 && !$aJotInfo[$CNF['FIELD_MESSAGE']] && $this->_oDb->deleteJot($aJotInfo[$CNF['FIELD_MESSAGE_ID']], $this->_iProfileId)) {
+                $aResult['empty_jot'] = 1;
+                $this->onDeleteJot($aJotInfo);
+            } else
+                $this->onUpdateJot($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $aJotInfo[$CNF['FIELD_MESSAGE_ID']], $aJotInfo[$CNF['FIELD_MESSAGE_AUTHOR']]);
+        }
+
+        echoJson($aResult);
+    }
+
+    /**
+     * Leave talk with defined id
+     * @return string with json
+     */
+    public function actionLeave()
+    {
+        $iLotId = bx_get('lot');
+
+        if (!$iLotId || !$this->_oDb->isParticipant($iLotId, $this->_iProfileId)) {
+            return echoJson(['message' => _t('_bx_messenger_not_participant'), 'code' => 1]);
+        }
+
+        if ($this->_oDb->isAuthor($iLotId, $this->_iProfileId))
+            return echoJson(['message' => _t('_bx_messenger_cant_leave'), 'code' => 1]);
+
+
+        if ($this->_oDb->leaveLot($iLotId, $this->_iProfileId))
+            return echoJson(['message' => _t('_bx_messenger_successfully_left'), 'code' => 0]);
+    }
+
+    /**
+     * Block notifications from specified lot(conversation)
+     * @return string with json
+     */
+    public function actionMute()
+    {
+        $iLotId = bx_get('lot');
+
+        if ($iLotId && $this->isAvailable($iLotId)) {
+            $bMuted = $this->_oDb->muteLot($iLotId, $this->_iProfileId);
+            return echoJson(array('code' => $bMuted, 'title' => $bMuted ? _t('_bx_messenger_lots_menu_mute_info_on') : _t('_bx_messenger_lots_menu_mute_info_off')));
+        }
+
+        return echoJson(array('code' => 1));
+    }
+
+    /**
+     * Mark lot as favorite
+     * @return string with json
+     */
+    public function actionStar()
+    {
+        $iLotId = bx_get('lot');
+        if ($iLotId) {
+            $bStar = $this->_oDb->starLot($iLotId, $this->_iProfileId);
+            return echoJson(array('code' => $bStar, 'title' => !$bStar ? _t('_bx_messenger_lots_menu_star_on') : _t('_bx_messenger_lots_menu_star_off')));
+        }
+
+        return echoJson(array('code' => 1));
+    }
+
+    /**
+     * Returns number of lots with at least one unread message
+     * @return int
+     */
+    public function serviceGetUpdatedLotsNum($iProfileId = 0)
+    {
+        if (!$this->isLogged())
+            return array();
+
+        $aLots = $this->_oDb->getLotsWithUnreadMessages($iProfileId ? $iProfileId : $this->_iProfileId);
+        return sizeof($aLots);
+    }
+
+    /**
+     * Common function to send notifications to the Talk's participants
+     * @param $iLotId integer Talk's id
+     * @param $iJotId integer message id
+     * @return bool|string
+     */
+
+    private function sendMessageNotification($iLotId, $iJotId)
+    {
+        $aReceived = [];
+        if (!$iLotId || !$iJotId)
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+        $aJot = $this->_oDb->getJotById($iJotId);
+
+        $sMessage = $aJot[$CNF['FIELD_MESSAGE']];
+        if ($sMessage) {
+            $sMessage = preg_replace('/<br\s?\/?>/i', "\r\n", $sMessage);
+            $sMessage = html2txt($sMessage);
+            $sMessage = get_mb_substr($sMessage, 0, (int) $CNF['PARAM_PUSH_NOTIFICATIONS_DEFAULT_SYMBOLS_NUM']);
+
+            // find mentions in the message and send notifications
+            if ($CNF['USE_MENTIONS'] && preg_match_all('/<a[^>]*class="bx-mention[^"]*"[^>]*data-id="(\d+)".*?<\/a>/i', $aJot[$CNF['FIELD_MESSAGE']], $aMatches)) {
+                list(, $aMentionedProfiles) = $aMatches;
+
+                if ($aMentionedProfiles) {
+                    $this->sendNotification($iLotId, $iJotId, $sMessage, $aReceived, $aMentionedProfiles, BX_MSG_NTFS_MENTION);
+                    $aReceived = array_diff($aReceived, $aMentionedProfiles);
+                }
+            }
+        }
+
+        return $this->sendNotification($iLotId, $iJotId, $sMessage, $aReceived);
+    }
+
+    /**
+     * Sends push notification to the Talk's participants through the Notifications module or
+     * using Messenger's push notifications settings.
+     *
+     * @param $iLotId
+     * @param $iJotId
+     * @param $sMessage
+     * @param array $aReceived
+     * @param array $aRecipients
+     * @param string $sType
+     * @return bool|string
+     */
+    private function sendNotification($iLotId, $iJotId, $sMessage, $aReceived = [], $aRecipients = [], $sType = BX_MSG_NTFS_MESSAGE)
+    {
+        // check if the Notifications module is installed and send notifications through it
+        if ($this->_oDb->isModuleByName('bx_notifications'))
+            return $this->sendNotifications($iLotId, $iJotId, $aReceived, $aRecipients, $sType);
+    }
+
+    /**
+     * Proper the list of the participants whom to send notifications
+     * @param $iLotId
+     * @param $iJotId
+     * @param array $aOnlineUsers
+     * @param array $aRecipients
+     * @param string $sType
+     * @return bool
+     */
+
+    public function sendNotifications($iLotId, $iJotId, $aOnlineUsers = [], $aRecipients = [], $sType = BX_MSG_NTFS_MESSAGE)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        $sModule = $this->_oConfig->getObject('alert');
+        if (empty($aLotInfo))
+            return false;
+
+        $aPartList = $this->_oDb->getParticipantsList($iLotId, true, $this->_iProfileId);
+        if ((int) $aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_BROADCAST) {
+            $iJotCount = $this->_oDb->getJotsNumber($iLotId, 0);
+            if ($iJotCount >= 2)
+                $aPartList = $this->_oDb->getBroadcastParticipants($iLotId);
+        }
+
+        if (empty($aPartList))
+            return false;
+
+        if (!empty($aRecipients)) {
+            $aDiff = array_diff($aRecipients, $aPartList);
+            if (!empty($aDiff) && (int) $aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_PRIVATE) // in case if the mentioned profiles are not in the participants list
+                $aPartList = array_diff($aRecipients, $aDiff);
+            else
+                $aPartList = $aRecipients;
+        }
+
+        $bIsMention = ($sType == BX_MSG_NTFS_MENTION);
+        foreach ($aPartList as &$iPart) {
+            if (array_search($iPart, $aOnlineUsers) !== FALSE || $this->_oDb->isMuted($iLotId, $iPart))
+                continue;
+
+            $iCount = $this->_oDb->getSentNtfsNumber($iPart, $iLotId);
+            if ($iCount >= (int) $CNF['MAX_NTFS_NUMBER'] && !$bIsMention)
+                continue;
+
+            bx_alert($sModule, !$bIsMention ? 'got_jot_ntfs' : 'got_mention_ntfs', $iLotId, $this->_iProfileId, array(
+                'object_author_id' => $iPart,
+                'recipient_id' => $iPart,
+                'subobject_id' => $iJotId,
+                'source' => $sModule . '_' . $iJotId,
+                'source_mac' => $sModule . '_' . $this->_iProfileId . '_' . $iJotId,
+            ));
+        }
+
+        return true;
+    }
+
+    /**
+     * Creates template with member's avatar, name and etc... It is used when member posts a message to add message to member history immediately
+     * @return string json
+     */
+    public function actionLoadMembersTemplate()
+    {
+        $sTemplate = '';
+        if ($this->isLogged())
+            $sTemplate = $this->_oTemplate->getMembersJotTemplate($this->_iProfileId);
+
+        echoJson(
+            array(
+                'data' => $sTemplate
+            )
+        );
+    }
+
+    /**
+     * Delete all content by profile ID
+     * @param object oAlert
+     * @return boolean
+     */
+    public function serviceDeleteHistoryByAuthor($oAlert)
+    {
+        return $oAlert->iObject && !empty($oAlert->aExtras['delete_with_content']) ?
+            $this->_oDb->deleteProfileInfo($oAlert->iObject) : false;
+    }
+
+    /**
+     * Process alerts actions
+     * @param object oAlert
+     * @return boolean
+     */
+    public function serviceResponse($oAlert)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $iGroupProfileId = 0;
+        $iExecutor = 0;
+        $sModule = '';
+        if ($oAlert->sAction == 'fan_added') {
+            $iGroupProfileId = $oAlert->iObject;
+            $iExecutor = $oAlert->aExtras['performer_id'];
+            $sModule = $oAlert->sUnit;
+        } else if ($oAlert->sAction == 'connection_removed') {
+            $iGroupProfileId = BxDolProfile::getInstance($oAlert->aExtras['initiator'])->getContentId();
+            $iExecutor = $oAlert->iSender;
+            $sModule = str_replace('_fans', '', $oAlert->sUnit);
+        }
+
+        if (BxDolRequest::serviceExists($sModule, 'is_group_profile') && $iGroupProfileId) {
+            $aGroupInfo = BxDolService::call($sModule, 'get_info', array($iGroupProfileId, false));
+            if (!empty($aGroupInfo) && is_array($aGroupInfo)) {
+                $oModule = BxDolModule::getInstance($sModule);
+                if ($oModule->_oConfig) {
+                    $oMCNF = $oModule->_oConfig->CNF;
+                    $sUrl = "i={$oMCNF['URI_VIEW_ENTRY']}&id=" . $aGroupInfo[$oMCNF['FIELD_ID']];
+                    if ($sUrl && $aTalk = $this->_oDb->getLotByUrl($sUrl)) {
+                        if ($oAlert->sAction == 'fan_added' && BxDolService::call($sModule, 'check_allowed_view_for_profile', array($aGroupInfo)) === CHECK_ACTION_RESULT_ALLOWED) {
+                            if (!$this->_oDb->isParticipant($aTalk[$oMCNF['FIELD_ID']], $iExecutor))
+                                $this->_oDb->addMemberToParticipantsList($aTalk[$oMCNF['FIELD_ID']], $iExecutor);
+                        } else if ($oAlert->sAction == 'connection_removed' && $this->_oDb->isParticipant($aTalk[$oMCNF['FIELD_ID']], $iExecutor)) {
+                            $this->_oDb->removeParticipant($aTalk[$oMCNF['FIELD_ID']], $iExecutor);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Parse jot's link (repost)
+     * @param object oAlert
+     * @return string json
+     */
+    public function actionParseLink()
+    {
+        $sUrl = bx_get('link');
+        $iJotId = (int) bx_get('jot_id');
+        $bDontAttach = (int) bx_get('dont_attach');
+
+        $aUrl = $this->_oConfig->isJotLink($sUrl);
+        if (!empty($aUrl)) {
+            $aJotInfo = $this->_oDb->getJotById($aUrl['id']);
+            if (!$this->_oDb->isParticipant($aJotInfo[$this->_oConfig->CNF['FIELD_MESSAGE_FK']], $this->_iUserId))
+                return echoJson(array('code' => 0));
+
+            $sHTML = $this->_oTemplate->getJotAsAttachment($aUrl['id']);
+            if ($sHTML && !$bDontAttach)
+                $this->_oDb->addAttachment($iJotId, $aUrl['id'], BX_ATT_TYPE_REPOST);
+
+            return echoJson(array('code' => 0, 'html' => $sHTML));
+        }
+
+        echoJson(array('code' => 1));
+    }
+
+    /**
+     * Parses link from the message
+     * @param object oAlert
+     * @return json
+     */
+    public function actionGetAttachment()
+    {
+        $iJotId = (int) bx_get('jot_id');
+
+        if ($iJotId) {
+            $aJot = $this->_oDb->getJotById($iJotId);
+            if (!empty($aJot) && $this->_oDb->isParticipant($aJot[$this->_oConfig->CNF['FIELD_MESSAGE_FK']], $this->_iProfileId)) {
+                if (($aAttachment = $this->_oTemplate->getAttachment($aJot, true)) && isset($aAttachment[BX_ATT_GROUPS_ATTACH]))
+                    return echoJson(['code' => 0, 'html' => $aAttachment[BX_ATT_GROUPS_ATTACH]]);
+            }
+        }
+
+        echoJson(['code' => 1]);
+    }
+
+    /**
+     * Returns HTML popup form for video recording
+     */
+    public function actionGetRecordVideoForm()
+    {
+        header('Content-type: text/html; charset=utf-8');
+        echo $this->_oTemplate->getVideoRecordingForm($this->_iUserId);
+        exit;
+    }
+
+    /**
+     * Allows to delete/upload files in messages
+     * @return string
+     */
+    public function actionUploadTempFile()
+    {
+        if (!$this->isLogged()) {
+            echo _t('_bx_messenger_not_logged');
+            exit;
+        }
+
+        $CNF = &$this->_oConfig->CNF;
+        switch ($_SERVER['REQUEST_METHOD']) {
+            case 'DELETE':
+                if ($sName = @file_get_contents("php://input"))
+                    parse_str($sName, $aName);
+
+                if (!empty($aName) && @unlink(BX_DIRECTORY_PATH_TMP . basename($aName['name'])))
+                    echo 'OK';
+
+                break;
+            case 'POST':
+                $oStorage = new BxMessengerStorage($CNF['OBJECT_STORAGE']);
+                if (!$oStorage) {
+                    echo 0;
+                    exit;
+                }
+
+                $aFiles = array_filter($_FILES, function ($aFile, $sName) use ($CNF) {
+                    return stripos($sName, $CNF['FILES_UPLOADER']) !== FALSE;
+                }, ARRAY_FILTER_USE_BOTH);
+
+                $aUploader = current($aFiles);
+                if (!empty($aUploader) && $oStorage->isValidFileExt($aUploader['name'])) {
+                    $sTempFile = $aUploader['tmp_name'];
+                    $sTargetFile = BX_DIRECTORY_PATH_TMP . basename($aUploader['name']);
+                    move_uploaded_file($sTempFile, $sTargetFile);
+                    echo 'OK';
+                }
+        }
+
+        return '';
+    }
+
+    /**
+     * Uploads recorded video file
+     */
+    public function actionUploadVideoFile()
+    {
+        $oStorage = new BxMessengerStorage($this->_oConfig->CNF['OBJECT_STORAGE']);
+        if (!$oStorage || !isset($_POST['name']) || empty($_FILES)) {
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_send_message_no_video')));
+        }
+
+        if ($_FILES['file'] && $oStorage->isValidFileExt($_FILES['file']['name'])) {
+            $sTempFile = $_FILES['file']['tmp_name'];
+            $sTargetFile = BX_DIRECTORY_PATH_TMP . basename($_POST['name']);
+
+            if (move_uploaded_file($sTempFile, $sTargetFile))
+                return echoJson(array('code' => 0));
+        }
+
+        echoJson(array('code' => 1, 'message' => _t('_bx_messenger_send_message_no_video')));
+    }
+
+    /**
+     * Checks is the file has valid extension to upload.
+     */
+    public function actionIsValidFile()
+    {
+        $oStorage = new BxMessengerStorage($this->_oConfig->CNF['OBJECT_STORAGE']);
+        if (!$oStorage)
+            return echoJson(array('code' => 1));
+
+        $sFileName = bx_get('name');
+        if ($sFileName && (int) $oStorage->isValidFileExt($sFileName)) {
+            $sIconFile = $oStorage->getFontIconNameByFileName($sFileName);
+            return echoJson(array('code' => 0, 'thumbnail' => $sIconFile ? $sIconFile : '', 'is_image' => (int) $oStorage->isImageExt($sFileName)));
+        }
+
+        echoJson(array('code' => 1));
+    }
+
+    /**
+     * Allows to download files from the talk
+     * @param $iFileId
+     * @return string
+     */
+
+    public function actionDownloadFile($iFileId)
+    {
+        if (!$iFileId || !$this->_iProfileId)
+            return '';
+
+        $CNF = &$this->_oConfig->CNF;
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+
+        $aFile = $oStorage->getFile((int) $iFileId);
+        if (empty($aFile)) {
+            echo _t('_bx_messenger_post_file_not_found');
+            exit;
+        }
+
+        if (isset($aFile[$CNF['FIELD_ST_JOT']])) {
+            $iLotId = $this->_oDb->getLotByJotId($aFile[$CNF['FIELD_ST_JOT']], true);
+            if (!$iLotId || !$this->_oDb->isParticipant($iLotId, $this->_iProfileId)) {
+                echo _t('_bx_messenger_not_participant');
+                exit;
+            }
+        }
+
+        $sToken = $oStorage->genToken($iFileId);
+        return $oStorage->download($aFile[$CNF['FIELD_ST_REMOTE']], $sToken);
+    }
+
+    /**
+     * Allow to zoom image from talk's history
+     * @param int $iStorageId file id
+     * @param int $iWidth width of the window
+     * @param int $iHeight height of the window
+     * @return string html content
+     */
+    function actionGetBigImage($iStorageId, $iWidth, $iHeight)
+    {
+        if (!$iStorageId)
+            return '';
+
+        $iWidth = (int) $iWidth * 0.9;
+        $iHeight = (int) $iHeight * 0.9;
+
+        $oStorage = BxDolStorage::getObjectInstance($this->_oConfig->CNF['OBJECT_STORAGE']);
+        $aFile = $oStorage->getFile((int) $iStorageId);
+
+        $sFileUrl = $oStorage->getFileUrlById($iStorageId);
+        if (!empty($aFile)) {
+            $aInfo = @getimagesize($sFileUrl);
+            if (empty($aInfo)) {
+                echo MsgBox(_t('_bx_messenger_post_file_not_found'));
+                exit;
+            }
+
+            if ($aInfo[0] <= $iWidth && $aInfo[1] <= $iHeight) {
+                $iWidth = (int) $aInfo[0];
+                $iHeight = (int) $aInfo[1];
+            } else {
+                $fXRatio = $aInfo[0] / $iWidth;
+                $fYRatio = $aInfo[1] / $iHeight;
+
+                if ($fXRatio > $fYRatio)
+                    $iHeight = 'auto';
+                else
+                    $iWidth = 'auto';
+            }
+        }
+
+        echo $this->_oTemplate->parseHtmlByName('big_image.html', array(
+            'height' => $iHeight,
+            'width' => $iWidth,
+            'url' => $oStorage->getFileUrlById((int) $iStorageId)
+        ));
+        exit;
+    }
+
+    /**
+     * Allows to register Messenger's actions in Notifications module through the Alerts
+     * @return array
+     */
+    public function serviceGetNotificationsData()
+    {
+        $sModule = $this->_aModule['name'];
+        return [
+            'handlers' => [
+                ['group' => $sModule, 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'got_jot_ntfs', 'module_name' => $sModule, 'module_method' => 'get_message_content', 'module_class' => 'Module'],
+                ['group' => $sModule, 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'delete_jot_ntfs'],
+                ['group' => "{$sModule}_mention", 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'got_mention_ntfs', 'module_name' => $sModule, 'module_method' => 'get_message_content', 'module_class' => 'Module'],
+                ['group' => "{$sModule}_mention", 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'delete_mention_ntfs'],
+                ['group' => "{$sModule}_broadcast", 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'got_broadcast_ntfs', 'module_name' => $sModule, 'module_method' => 'get_broadcast_content', 'module_class' => 'Module'],
+                ['group' => "{$sModule}_broadcast", 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'delete_broadcast_ntfs']
+            ],
+            'settings' => [
+                ['group' => $sModule, 'unit' => $sModule, 'action' => 'got_jot_ntfs', 'types' => ['personal']],
+                ['group' => "{$sModule}_mention", 'unit' => $sModule, 'action' => 'got_mention_ntfs', 'types' => ['personal']],
+                ['group' => "{$sModule}_broadcast", 'unit' => $sModule, 'action' => 'got_broadcast_ntfs', 'types' => ['personal']]
+            ],
+            'alerts' => [
+                ['unit' => $sModule, 'action' => 'got_jot_ntfs'],
+                ['unit' => $sModule, 'action' => 'delete_jot_ntfs'],
+                ['unit' => $sModule, 'action' => 'got_mention_ntfs'],
+                ['unit' => $sModule, 'action' => 'delete_mention_ntfs'],
+                ['unit' => $sModule, 'action' => 'got_broadcast_ntfs'],
+                ['unit' => $sModule, 'action' => 'delete_broadcast_ntfs']
+            ]
+        ];
+    }
+
+    public function serviceGetBroadcastContent($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aJotInfo = $this->_oDb->getJotById($aEvent['subobject_id']);
+        $aLotInfo = $this->_oDb->getLotByJotId($aEvent['subobject_id'], false);
+        if (empty($aJotInfo) || empty($aLotInfo))
+            return array();
+
+        $sEntryUrl = $this->_oConfig->getRepostUrl($aJotInfo[$CNF['FIELD_MESSAGE_ID']]);
+        $iType = $aLotInfo[$CNF['FIELD_TYPE']];
+
+        $sTitle = _t('_bx_messenger_broadcast_message_title');
+
+        // replace br to spaces and truncate the line
+        $sMessage = _t('_bx_messenger_broadcast_message_body');
+        $sTruncatedMessage = '';
+        if ($aJotInfo[$CNF['FIELD_MESSAGE']]) {
+            $sTruncatedMessage = strmaxtextlen(preg_replace('/<br\W*?\/>|\n/', " ", $aJotInfo[$CNF['FIELD_MESSAGE']]), $CNF['PARAM_MAX_JOT_NTFS_MESSAGE_LENGTH']);
+            $sMessage = _t('_bx_messenger_txt_sample_comment_single', $sTruncatedMessage);
+        }
+
+        $aResult = [
+            'entry_sample' => _t('_bx_messenger_message'),
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sTitle,
+            'entry_author' => $aEvent['object_owner_id'],
+            'subentry_sample' => $sMessage,
+            'lang_key' => '_bx_messenger_txt_subobject_added_broadcast'
+        ];
+
+        $sSubject = _t("_bx_messenger_notification_subject_broadcast", BxDolProfile::getInstanceMagic($aEvent['owner_id'])->getDisplayName());
+        $sAlterBody = $sTruncatedMessage ?: _t('_bx_messenger_txt_sample_email_push', html2txt($sMessage));
+
+        $aResult['lang_key'] = [
+            'site' => $aResult['lang_key'],
+            'email' => $sAlterBody,
+            'push' => $sAlterBody
+        ];
+
+        $aResult['settings'] = [
+            'email' => [
+                'subject' => $sSubject
+            ],
+            'push' => [
+                'subject' => $sSubject
+            ]
+        ];
+
+        bx_alert(
+            $this->_oConfig->getObject('alert'),
+            'before_broadcast_notification',
+            $aEvent['subobject_id'],
+            $aEvent['object_owner_id'],
+            ['data' => &$aResult, 'talk' => $aLotInfo, 'message' => $aJotInfo]
+        );
+
+        return $aResult;
+    }
+    /**
+     * Jot info for Notifications module
+     * @param array Input alert params
+     * @return array with info for notifications
+     */
+    public function serviceGetMessageContent($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aJotInfo = $this->_oDb->getJotById($aEvent['subobject_id']);
+        $aLotInfo = $this->_oDb->getLotByJotId($aEvent['subobject_id'], false);
+        if (empty($aJotInfo) || empty($aLotInfo))
+            return array();
+
+        $sType = BX_MSG_NTFS_MESSAGE;
+        if ($aEvent['action'] === 'got_mention_ntfs')
+            $sType = BX_MSG_NTFS_MENTION;
+
+        $sEntryUrl = $this->_oConfig->getRepostUrl($aJotInfo[$CNF['FIELD_MESSAGE_ID']]);
+        $sEntryUrlApi = $this->_oConfig->getRepostUrlApi($aLotInfo[$CNF['FIELD_HASH']]);
+        $iType = $aLotInfo[$CNF['FIELD_TYPE']];
+
+        $sTitle = isset($aLotInfo[$CNF['FIELD_TITLE']]) && $aLotInfo[$CNF['FIELD_TITLE']] ?
+            $aLotInfo[$CNF['FIELD_TITLE']] : _t('_bx_messenger_lots_private_lot');
+
+        if ($this->_oDb->isLinkedTitle($iType)) {
+            $sTitle = $this->_oDb->isLinkedTitle($iType) ? _t('_bx_messenger_linked_title', $sTitle) : _t($sTitle);
+            $sEntryUrl = $this->_oConfig->getPageLink($aLotInfo[$CNF['FIELD_URL']]);
+        }
+
+        $bAttachmentFiles = $this->_oConfig->isAttachmentType($aJotInfo, BX_ATT_TYPE_FILES);
+        $bAttachmentGiphy = $this->_oConfig->isAttachmentType($aJotInfo, BX_ATT_TYPE_GIPHY);
+        $bAttachmentReply = $this->_oConfig->isAttachmentType($aJotInfo, BX_ATT_TYPE_REPLY);
+        $bAttachmentRepost = $this->_oConfig->isAttachmentType($aJotInfo, BX_ATT_TYPE_REPOST);
+
+        $sTruncatedMessage = $sMessage = '';
+        // replace br to spaces and truncate the line
+        if ($aJotInfo[$CNF['FIELD_MESSAGE']] && !$bAttachmentRepost) {
+            $sTruncatedMessage = strmaxtextlen(preg_replace('/<br\W*?\/>|\n/', " ", $aJotInfo[$CNF['FIELD_MESSAGE']]), $CNF['PARAM_MAX_JOT_NTFS_MESSAGE_LENGTH']);
+            $sMessage = _t('_bx_messenger_txt_sample_comment_single', $sTruncatedMessage);
+        }
+
+        if ($bAttachmentFiles)
+            $sMessage = _t('_bx_messenger_txt_sample_comment_file_single', $this->_oDb->getJotFiles($aJotInfo[$CNF['FIELD_MESSAGE_ID']], true));
+        elseif ($bAttachmentGiphy)
+            $sMessage = _t('_bx_messenger_txt_sample_comment_giphy_single');
+        elseif ($bAttachmentReply)
+            $sMessage = _t('_bx_messenger_txt_sample_comment_reply_single');
+        elseif ($bAttachmentRepost)
+            $sMessage = _t('_bx_messenger_txt_sample_comment_repost_single');
+
+        $aResult = array(
+            'entry_sample' => _t('_bx_messenger_message'),
+            'entry_url' => $sEntryUrl,
+            'entry_url_api' => $sEntryUrlApi,
+            'entry_caption' => $sTitle,
+            'entry_author' => $aEvent['object_owner_id'],
+            'subentry_sample' => $sMessage,
+            'lang_key' => $bAttachmentFiles || $bAttachmentGiphy
+                ? '_bx_messenger_txt_subobject_added_single' : "_bx_messenger_txt_subobject_added_{$sType}"
+        );
+
+        $sSubject = _t("_bx_messenger_notification_subject_{$sType}", BxDolProfile::getInstanceMagic($aEvent['owner_id'])->getDisplayName());
+
+        $sAlterBody = $sTruncatedMessage ? $sTruncatedMessage : _t('_bx_messenger_txt_sample_email_push', html2txt($sMessage));
+
+        $aResult['lang_key'] = array(
+            'site' => $aResult['lang_key'],
+            'email' => $sAlterBody,
+            'push' => $sAlterBody
+        );
+
+        $aResult['settings'] = array(
+            'email' => array(
+                'subject' => $sSubject
+            ),
+            'push' => array(
+                'subject' => $sSubject
+            )
+        );
+
+        bx_alert(
+            $this->_oConfig->getObject('alert'),
+            'before_message_notification',
+            $aEvent['subobject_id'],
+            $aEvent['object_owner_id'],
+            ['data' => &$aResult, 'talk' => $aLotInfo, 'message' => $aJotInfo]
+        );
+
+
+        return $aResult;
+    }
+
+    /**
+     * Check if the user can read Lot messages
+     * @param array $aDataEntry contains Lot details
+     * @param boolean $isPerformAction used for compatibility with parent method
+     * @param bool $iProfileId profile id of the member show has to get notification
+     * @return int
+     */
+
+    public function serviceCheckAllowedViewForProfile($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        $mixedResult = null;
+
+        bx_alert(
+            'system',
+            'check_allowed_view',
+            0,
+            0,
+            array(
+                'module' => $this->getName(),
+                'content_info' => $aDataEntry,
+                'profile_id' => $iProfileId,
+                'override_result' => &$mixedResult
+            )
+        );
+
+        if ($mixedResult !== null)
+            return $mixedResult;
+
+        $CNF = &$this->_oConfig->CNF;
+        if (empty($aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return $this->_oDb->isParticipant($aDataEntry[$CNF['FIELD_ID']], $iProfileId, true) === TRUE
+            || $aDataEntry[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PUBLIC
+            || $aDataEntry[$CNF['FIELD_TYPE']] == BX_IM_TYPE_SETS ? CHECK_ACTION_RESULT_ALLOWED : CHECK_ACTION_RESULT_NOT_ALLOWED;
+    }
+
+    /**
+     * Execute the list of actions which are required when message is sent
+     * @param $iJotId
+     * @return false
+     */
+    public function onSendJot($iJotId)
+    {
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_messenger-send_jot 'bx_messenger', 'send_jot' - hook after a jot (message) was sent
+         * - $unit_name - equals `bx_messenger`
+         * - $action - equals `send_jot`
+         * - $object_id - jot (message) id
+         * - $sender_id - jot (message) author profile id
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `jot_info` - [array] jot (message) info array as key&value pairs
+         * @hook @ref hook-bx_messenger-send_jot
+         */
+        bx_alert($this->_oConfig->getObject('alert'), 'send_jot', $iJotId, $this->_iProfileId, [
+            'jot_info' => $aJotInfo
+        ]);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $aConvoInfo = $this->_oDb->getLotByJotId($iJotId, false);
+        $iLotId = $aConvoInfo[$CNF['FIELD_ID']];
+        if (!$iLotId)
+            return false;
+
+        $aPartList = $this->_oDb->getParticipantsList($iLotId, true, $this->_iProfileId);
+        if ((int) $aConvoInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_BROADCAST) {
+            $iJotCount = $this->_oDb->getJotsNumber($iLotId, 0);
+            if ($iJotCount >= 2)
+                $aPartList = $this->_oDb->getBroadcastParticipants($iLotId);
+            else
+                return false;
+        }
+
+        if (empty($aPartList))
+            return false;
+
+        foreach ($aPartList as &$iPart)
+            /**
+             * @hooks
+             * @hookdef hook-bx_messenger-got_jot 'bx_messenger', 'got_jot' - hook after a lot (conversation) got a new jot (message)
+             * - $unit_name - equals `bx_messenger`
+             * - $action - equals `got_jot`
+             * - $object_id - lot (conversation) id
+             * - $sender_id - jot (message) author profile id
+             * - $extra_params - array of additional params with the following array keys:
+             *      - `recipient_id` - [int] recipient profile id
+             *      - `subobject_id` - [int] jot (message) id
+             *      - `subobject_info` - [array] jot (message) info array as key&value pairs
+             * @hook @ref hook-bx_messenger-got_jot
+             */
+            bx_alert($this->_oConfig->getObject('alert'), 'got_jot', $iLotId, $this->_iProfileId, [
+                'recipient_id' => $iPart,
+                'subobject_id' => $iJotId,
+                'subobject_info' => $aJotInfo
+            ]);
+
+        if (!$this->_oDb->getIntervalJotsCount($iLotId, $iJotId))
+            $this->sendMessageNotification($iLotId, $iJotId);
+    }
+
+    public function onDeleteJot($aJotInfo)
+    {
+        if (empty($aJotInfo))
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $iLotId = $aJotInfo[$CNF['FIELD_MESSAGE_FK']];
+        $iJotId = $aJotInfo[$CNF['FIELD_MESSAGE_ID']];
+
+        $iProfileId = $aJotInfo[$CNF['FIELD_MESSAGE_AUTHOR']];
+
+        $sModule = $this->_oConfig->getObject('alert');
+
+        bx_alert($sModule, 'delete_jot', $iJotId, $this->_iProfileId, ['author_id' => $iProfileId, 'lot_id' => $iLotId]);
+        bx_alert($sModule, 'delete_jot_ntfs', $iLotId, $iProfileId, ['subobject_id' => $iJotId]);
+        bx_alert($sModule, 'delete_mention_ntfs', $iLotId, $iProfileId, ['subobject_id' => $iJotId]);
+
+        $this->_oDb->removeNotifications($iJotId);
+    }
+
+    public function onReactJot($iLotId, $iJotId, $iAuthor, $sAction = 'add')
+    {
+        bx_alert($this->_oConfig->getObject('alert'), "reaction_" . $sAction, $iJotId, $this->_iProfileId, array('author_id' => $iAuthor, 'lot_id' => $iLotId));
+    }
+
+    public function onUpdateJot($iLotId, $iJotId, $iProfileId = 0)
+    {
+        bx_alert($this->_oConfig->getObject('alert'), 'update_jot', $iJotId, $this->_iUserId, array('author_id' => $iProfileId, 'lot_id' => $iLotId));
+    }
+
+    public function onCreateLot($iLotId)
+    {
+        bx_alert($this->_oConfig->getObject('alert'), 'create_lot', $iLotId, $this->_iProfileId, $this->_oDb->getLotInfoById($iLotId));
+    }
+
+    public function onDeleteLot($aData)
+    {
+        if (empty($aData))
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+        $iAuthorId = $aData[$CNF['FIELD_AUTHOR']];
+        $iLotId = $aData[$CNF['FIELD_ID']];
+
+        bx_alert($this->_oConfig->getObject('alert'), 'delete_lot', $iLotId, $this->_iProfileId, ['author_id' => $iAuthorId]);
+    }
+
+    public function onAddNewParticipant($iLotId, $iParticipant, $iProfileId = 0)
+    {
+        bx_alert($this->_oConfig->getObject('alert'), 'add_part', $iParticipant, $this->_iUserId, array('lot_id' => $iLotId, 'author_id' => $iProfileId));
+    }
+
+    public function onRemoveParticipant($iLotId, $iParticipant, $iProfileId = 0)
+    {
+        bx_alert($this->_oConfig->getObject('alert'), 'remove_part', $iParticipant, $this->_iUserId, array('lot_id' => $iLotId, 'author_id' => $iProfileId));
+    }
+
+    /**
+     * Checks if logged member can contact with the person
+     * @param $iSender
+     * @param $iRecipient
+     * @return bool|mixed|true
+     */
+    public function onCheckContact($iSender, $iRecipient)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oSenderProfile = BxDolProfile::getInstance($iSender);
+        $oRecipientProfile = BxDolProfile::getInstance($iRecipient);
+        if ($CNF['CONTACT-JOIN-ORGANIZATION'] && $oSenderProfile && $oRecipientProfile) {
+            $aSenderProfileInfo = $oSenderProfile->getInfo();
+            $aRecipientProfileInfo = $oRecipientProfile->getInfo();
+
+            if ($aSenderProfileInfo['type'] === 'bx_organizations' || $aRecipientProfileInfo['type'] === 'bx_organizations') {
+                if (BxDolConnection::getObjectInstance("bx_organizations_fans")->isConnected($iSender, $iRecipient, true) || $aSenderProfileInfo['type'] === 'bx_organizations')
+                    return true;
+
+                return BxDolConnection::getObjectInstance("sys_profiles_subscriptions")->isConnected($iSender, $iRecipient);
+            }
+        }
+
+        if ($CNF['DISABLE-PROFILE-PRIVACY'])
+            return true;
+
+        if (method_exists('BxDolProfile', 'checkAllowedProfileContact')) {
+            $mixedResult = BxDolProfile::getInstance($iSender)->checkAllowedProfileContact($iRecipient);
+            if ($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+                return false;
+        }
+
+        $bCanContact = true;
+        bx_alert($this->_oConfig->getObject('alert'), 'check_contact', 0, false, array('can_contact' => &$bCanContact, 'sender' => $iSender, 'recipient' => $iRecipient, 'where' => $this->_oConfig->getName()));
+        return $bCanContact;
+    }
+
+    /**
+     * Check if the selected profile or any groups module's item is available to send messages for logged member
+     * @param $mixedObject integer Profile id
+     * @return bool|int|mixed|true
+     */
+    public function serviceIsContactAllowed($mixedObject)
+    {
+        if (!$this->_iProfileId)
+            return false;
+
+        $oProfile = BxDolProfile::getInstance($mixedObject);
+        $sModule = $oProfile->getModule();
+
+        if (BxDolRequest::serviceExists($sModule, 'act_as_profile') && BxDolService::call($sModule, 'act_as_profile'))
+            return $this->onCheckContact($this->_iProfileId, (int) $mixedObject);
+
+        if (BxDolRequest::serviceExists($sModule, 'is_group_profile') && BxDolService::call($sModule, 'is_group_profile')) {
+            $aOwnerInfo = BxDolService::call($sModule, 'get_info', array($oProfile->getContentId(), false));
+            if (empty($aOwnerInfo) || !is_array($aOwnerInfo))
+                return CHECK_ACTION_RESULT_ALLOWED;
+
+            return BxDolService::call($sModule, 'check_allowed_view_for_profile', array($aOwnerInfo)) === CHECK_ACTION_RESULT_ALLOWED;
+        }
+
+        return true;
+    }
+
+    /**
+     * Checks if video conference is available for member
+     * @param $mixedObject integer profile id
+     * @return bool
+     */
+    public function serviceIsVideoConferenceAllowed($mixedObject)
+    {
+        if (!$this->_iProfileId || !$this->onCheckContact($this->_iProfileId, (int) $mixedObject))
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+        if (isset($_SERVER['REQUEST_URI'])) {
+            $sRoom = $this->_oConfig->getRoomId($_SERVER['REQUEST_URI']);
+            $aRoom = $this->_oDb->getPublicVideoRoom($sRoom);
+            $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_IM_VC, $this->_iProfileId);
+            if ((empty($aRoom) || !(int) $aRoom[$CNF['FPJVC_STATUS']]) && $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_IM_VC, $this->_iProfileId) !== true)
+                return false;
+
+            if (!empty($aRoom) && (int) $aRoom[$CNF['FPJVC_STATUS']] && (int) $mixedObject !== $this->_iProfileId && $this->_oConfig->isAllowedAction(BX_MSG_ACTION_JOIN_IM_VC, $this->_iProfileId) !== true)
+                return false;
+        } else
+            return false;
+
+        $this->_oTemplate->addCss(array('video-conference.css'));
+        $this->_oTemplate->addJs('utils.js');
+        return true;
+    }
+
+    /**
+     * Sends immediate push notifications when Profile conference has been started
+     * @param $iObjectId
+     * @param $iSenderId
+     * @param $iReceiverId
+     * @param string $sType
+     * @return false
+     */
+    private function sendProfilePush($iObjectId, $iSenderId, $iReceiverId, $sType = 'bx_persons')
+    {
+        $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+        $sLanguage = $oLanguage->getCurrentLangName(false);
+
+        $oObject = BxDolProfile::getInstance($iObjectId);
+        if (!$oObject)
+            return false;
+
+        $oReceiver = BxDolProfile::getInstance($iReceiverId);
+        if (!$oReceiver)
+            return false;
+
+        $oSender = BxDolProfile::getInstance($iSenderId);
+        if (!$oSender)
+            return false;
+
+        $sMessage = _t("_bx_messenger_public_vc_{$sType}_message", $sType === 'bx_persons' ? $oSender->getDisplayName() : $oObject->getDisplayName());
+
+        $aContent = array(
+            'en' => $sMessage,
+        );
+
+        if (!isset($aContent[$sLanguage]))
+            $aContent[$sLanguage] = $sMessage;
+
+        $aHeadings = array($sLanguage => _t("_bx_messenger_push_vc_{$sType}_message_title"));
+
+        $aInfo = array(
+            'contents' => $aContent,
+            'headings' => $aHeadings,
+            'url' => $oObject->getUrl()
+        );
+
+        BxDolPush::getObjectInstance()->send(
+            $iReceiverId,
+            array_merge($aInfo, array(
+                'icon' => $sType === 'bx_persons' ?
+                    $oSender->getThumb() :
+                    $oObject->getThumb()
+            )),
+            true
+        );
+    }
+
+    public function actionGetVideoConferenceForm($iProfileId)
+    {
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        header('Content-type: text/html; charset=utf-8');
+
+        $sContent = '';
+        if (!$oProfile)
+            $sContent = MsgBox(_t('_bx_messenger_profile_not_found'));
+        else {
+            $aInfo = $oProfile->getInfo();
+            if ($aInfo['type'] == 'bx_organizations') {
+                $aFans = BxDolConnection::getObjectInstance("{$aInfo['type']}_fans")->getConnectedContent($iProfileId, true);
+                if (!empty($aFans)) {
+                    foreach ($aFans as &$iPart)
+                        $this->sendProfilePush($iProfileId, $this->_iProfileId, $iPart, $aInfo['type']);
+                }
+            } else if ($this->_iProfileId != $iProfileId)
+                $this->sendProfilePush($iProfileId, $this->_iProfileId, $iProfileId, $aInfo['type']);
+
+            $CNF = &$this->_oConfig->CNF;
+            $sIdent = $this->getReferrerPageIdent();
+            $sRoom = $this->_oConfig->getRoomId($sIdent);
+            $aRoom = $this->_oDb->getPublicVideoRoom($sRoom);
+            if (empty($aRoom) || !(int) $aRoom[$CNF['FPJVC_STATUS']]) {
+                $mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_IM_VC, $this->_iProfileId);
+                if ($mixedResult !== true)
+                    $sContent = $mixedResult;
+            } else
+                if (!empty($aRoom) && (int) $aRoom[$CNF['FPJVC_STATUS']] && (int) $iProfileId !== $this->_iProfileId) {
+                    $mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_JOIN_IM_VC, $this->_iProfileId);
+                    if ($mixedResult !== true)
+                        $sContent = $mixedResult;
+                }
+        }
+
+        echo $sContent ? $sContent : $this->_oTemplate->getPublicJitsi($this->_iProfileId, $sIdent, $oProfile->getDisplayName(), 'bx-messenger-vc-call');
+        exit;
+    }
+
+    private function getReferrerPageIdent()
+    {
+        $sPath = '';
+        if (isset($_SERVER['HTTP_REFERER'])) {
+            $aPath = parse_url($_SERVER['HTTP_REFERER']);
+            if (isset($aPath['path']))
+                $sPath = $aPath['path'];
+
+            if (isset($aPath['query']))
+                $sPath .= '?' . $aPath['query'];
+
+        }
+
+        return $sPath;
+    }
+
+    public function actionUpdatePublicJitsiVideoConference()
+    {
+        $sRoom = bx_get('room');
+        $sAction = bx_get('action');
+        if (!$sRoom || !$this->_iProfileId || !$sAction)
+            return false;
+
+        if ($sAction == BX_JOT_PUBLIC_JITSI_JOIN)
+            return $this->_oDb->createPublicVideoRoom($sRoom, $this->_iProfileId);
+
+        return $this->_oDb->updatePublicVideoRoom($sRoom, $this->_iProfileId, $sAction);
+    }
+
+    public function serviceGetLiveUpdates($aMenuItemParent, $aMenuItemChild, $iCount = 0)
+    {
+        $iCountNew = $this->serviceGetUpdatedLotsNum();
+        if ($iCountNew == $iCount)
+            return false;
+
+        return array(
+            'count' => $iCountNew,
+            'method' => 'bx_menu_show_live_update(oData)',
+            'data' => array(
+                'code' => BxDolTemplate::getInstance()->parseHtmlByTemplateName('menu_item_addon', array(
+                    'content' => '{count}'
+                )),
+                'mi_parent' => $aMenuItemParent,
+                'mi_child' => $aMenuItemChild
+            ),
+        );
+    }
+
+    public function serviceGetLiveVcUpdates($iCount = 0)
+    {
+        $iCount = 0;
+        if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
+            $sPageUrl = $this->getReferrerPageIdent();
+            /* ADD ABILITY TO call members when they on any page of the site and have incoming call */
+            if (preg_match('/.*page\/(.*)\?id=(\d+).*/', $sPageUrl, $aParams)) {
+                if (!empty($aParams) && isset($aParams[1]) && isset($aParams[2])) {
+                    $iPageOwner = $this->_oDb->findThePageOwner(array('i' => $aParams[1], 'id' => $aParams[2]));
+                    //if ($this->_iProfileId == $iPageOwner)
+                }
+            }
+            $aParticipants = $this->_oDb->getPublicRoomParticipants($this->_oConfig->getRoomId($sPageUrl));
+            $iCount = count($aParticipants);
+        }
+
+        return array(
+            'count' => $iCount,
+            'method' => 'oMUtils.addBubble(oData)',
+            'data' => array(
+                'code' => BxDolTemplate::getInstance()->parseHtmlByTemplateName('menu_item_addon', array(
+                    'content' => '{count}'
+                )),
+                'selector' => '.bx-menu-item-public-vc-messenger a.bx-btn'
+            ),
+        );
+    }
+
+    /**
+     * @param int $iObjectId item's id
+     * @param strign $sType name of te module
+     * @param int $iProfileId comments' author, if this param is empty, then get all comments
+     * @param int $iStart get comments from position
+     * @param int $iPerPage number of the comments to get at once
+     * @return array comments list
+     */
+    public function serviceGetLiveComments($iObjectId, $sType, $iProfileId = 0, $iStart = 0, $iPerPage = 0)
+    {
+        $aComments = $this->_oDb->getLiveComments($iObjectId, $sType, $iProfileId, $iStart, $iPerPage);
+        if (!(int) $aComments['total'])
+            return array(
+                'items' => array(),
+                'id' => $iObjectId,
+                'type' => $sType,
+                'total' => 0
+            );
+
+        $aResult = array();
+        foreach ($aComments['result'] as $iKey => $aValue) {
+            $oProfile = $this->_oTemplate->getObjectUser($aValue[$this->_oConfig->CNF['FIELD_LCMTS_AUTHOR']]);
+            $aResult[$aValue[$this->_oConfig->CNF['FIELD_LCMTS_ID']]] = array(
+                'text' => $aValue[$this->_oConfig->CNF['FIELD_LCMTS_TEXT']],
+                'author' => array(
+                    'id' => $aValue[$this->_oConfig->CNF['FIELD_LCMTS_AUTHOR']],
+                    'name' => $oProfile->getDisplayName(),
+                    'icon' => $oProfile->getAvatar(),
+                    'url' => $oProfile->getUrl()
+                ),
+                'id' => $aValue[$this->_oConfig->CNF['FIELD_LCMTS_ID']],
+                'date' => $aValue[$this->_oConfig->CNF['FIELD_LCMTS_DATE']],
+            );
+        }
+
+        return array('items' => $aResult, 'id' => $iObjectId, 'type' => $sType, 'total' => (int) $aComments['total']);
+    }
+
+    /**
+     * @param string $sText text of the comment
+     * @param int $iObjectId item's id
+     * @param string $sType $sType name of te module
+     * @param int $iProfileId comment's author
+     * @param int $iDate client's time when message is sent
+     * @return int added object's id
+     */
+    public function servicePostLiveComments($sText, $iObjectId, $sType, $iProfileId, $iDate = 0)
+    {
+        if (!$sText || !$iObjectId || !$sType || !$iProfileId)
+            return 0;
+
+        $aResult = array('code' => 1);
+        if ($iId = $this->_oDb->addLiveComment($sText, $iObjectId, $sType, $iProfileId, $iDate))
+            $aResult = array('code' => 0, 'id' => $iId, 'date' => $iDate ? $iDate : time());
+
+        return $aResult;
+    }
+
+    public function serviceRemoveLiveComment($iObjectId, $iProfileId)
+    {
+        if (!$iObjectId || !$iProfileId)
+            return false;
+
+        $aResult = array('code' => 0, 'id' => $iObjectId);
+        return $this->_oDb->removeLiveComment($iObjectId, $iProfileId) ? $aResult : array('code' => 1);
+    }
+
+    public function actionGetFilesList($iLotId)
+    {
+        if (!$iLotId || !$this->_iProfileId || !$this->_oDb->isParticipant($iLotId, $this->_iProfileId))
+            return false;
+
+        $aFiles = $this->_oDb->getLotFiles($iLotId);
+        if (empty($aFiles))
+            return array();
+
+        $aResult = array();
+        $CNF = &$this->_oConfig->CNF;
+        foreach ($aFiles as $iKey => $aValue)
+            $aResult[$aValue[$CNF['FIELD_ST_ID']]] = $this->_oTemplate->getFileContent($aValue);
+
+        return $aResult;
+    }
+
+    public function actionGetLotTabs($iLotId, $sAction)
+    {
+        if (!$iLotId || !$this->_iUserId || !$this->_oDb->isParticipant($iLotId, $this->_iUserId))
+            return echoJson(array('code' => 1, 'msg' => _t('_bx_messenger_no_permissions')));
+
+        $sHTML = '';
+        switch ($sAction) {
+            case 'files':
+                $aFiles = $this->actionGetFilesList($iLotId);
+                if (empty($aFiles))
+                    $sHTML = MsgBox(_t('_bx_messenger_txt_msg_no_results'));
+                else
+                    $sHTML = $this->_oTemplate->parseHtmlByName('files-feeds.html', array('bx_repeat:files' => $aFiles));
+                break;
+        }
+
+        header('Content-type: text/html; charset=utf-8');
+        echo $sHTML;
+        exit;
+    }
+
+    /************ REACT Jot Integration *************************************/
+
+    public function serviceGetLotsList($iProfileId)
+    {
+        if (!$iProfileId)
+            return '';
+
+        $aLots = $this->_oDb->getMyLots($iProfileId);
+        if (empty($aLots))
+            return array();
+
+        $aLotsList = array();
+        foreach ($aLots as &$aLot) {
+            $aParticipantsList = $this->_oDb->getParticipantsList($aLot[$this->_oConfig->CNF['FIELD_ID']], true, $iProfileId);
+
+            $iParticipantsCount = count($aParticipantsList);
+            $aParticipantsList = $iParticipantsCount ? array_slice($aParticipantsList, 0, $this->_oConfig->CNF['PARAM_ICONS_NUMBER']) : array($iProfileId);
+
+            $aNickNames = array();
+            foreach ($aParticipantsList as $iParticipant) {
+                $oProfile = $this->_oTemplate->getObjectUser($iParticipant);
+                if ($oProfile) {
+                    $aNickNames[] = array(
+                        'name' => $oProfile->getDisplayName(),
+                        'url' => $oProfile->getUrl(),
+                        'thumb' => $oProfile->getThumb()
+                    );
+                }
+            }
+
+            if (!empty($aLot[$this->_oConfig->CNF['FIELD_TITLE']]))
+                $sTitle = _t($aLot[$this->_oConfig->CNF['FIELD_TITLE']]);
+            else {
+                $aTitle = array();
+                $aTmpNickNames = $iParticipantsCount > 3 ? array_slice($aNickNames, 0, $this->_oConfig->CNF['PARAM_ICONS_NUMBER']) : $aNickNames;
+                foreach ($aTmpNickNames as &$aValue)
+                    $aTitle[] = $aValue['name'];
+
+                $sTitle = implode(', ', $aTitle);
+            }
+
+
+            $sStatus = '';
+            if ($iParticipantsCount == 1 && $oProfile && empty($aLot[$this->_oConfig->CNF['FIELD_TITLE']]))
+                $sStatus = (int) (method_exists($oProfile, 'isOnline') ? $oProfile->isOnline() : false);
+            else
+                $sStatus = $iParticipantsCount;
+
+            $aLatestJots = $this->_oDb->getLatestJot($aLot[$this->_oConfig->CNF['FIELD_ID']]);
+            $iTime = $aLot[$this->_oConfig->CNF['FIELD_ADDED']];
+
+            $oSender = $sSender = $sMessage = '';
+            if (!empty($aLatestJots)) {
+                if (isset($aLatestJots[$this->_oConfig->CNF['FIELD_MESSAGE']])) {
+                    $sMessage = $aLatestJots[$this->_oConfig->CNF['FIELD_MESSAGE']];
+                    if ($aLatestJots[$this->_oConfig->CNF['FIELD_MESSAGE_AT_TYPE']] == BX_ATT_TYPE_REPOST) {
+                        $sMessage = $this->_oConfig->cleanRepostLinks($sMessage, $aLatestJots[$this->_oConfig->CNF['FIELD_MESSAGE_AT']]);
+                        $sMessage = $sMessage ? $sMessage : _t('_bx_messenger_repost_message');
+                    }
+
+                    $sMessage = BxTemplFunctions::getInstance()->getStringWithLimitedLength($sMessage, $this->_oConfig->CNF['MAX_PREV_JOTS_SYMBOLS']);
+                }
+
+                if (!$sMessage && $aLatestJots[$this->_oConfig->CNF['FIELD_MESSAGE_AT_TYPE']] == BX_ATT_TYPE_FILES)
+                    $sMessage = _t('_bx_messenger_attached_files_message', $this->_oDb->getJotFiles($aLatestJots[$this->_oConfig->CNF['FIELD_MESSAGE_ID']], true));
+
+                $sSender = '';
+                if ($oSender = $this->_oTemplate->getObjectUser($aLatestJots[$this->_oConfig->CNF['FIELD_MESSAGE_AUTHOR']]))
+                    $sSender = $oSender->id() == $iProfileId ? _t('_bx_messenger_you_username_title') : $oSender->getDisplayName();
+
+                $iTime = $aLatestJots[$this->_oConfig->CNF['FIELD_MESSAGE_ADDED']];
+            }
+
+            $aLotsList[] = array(
+                'participants_count' => $iParticipantsCount,
+                'participants_list' => $aNickNames,
+                'title' => $sTitle,
+                'lot_id' => $aLot[$this->_oConfig->CNF['FIELD_ID']],
+                'status' => $sStatus,
+                'latest_jot_message' => $sMessage,
+                'latest_jot_message_author' => $sSender,
+                'latest_jot_message_date' => $iTime,
+                'latest_jot_message_author_thumb' => $oSender ? $oSender->getThumb() : '',
+            );
+        }
+
+        return $aLotsList;
+    }
+
+    public function servicePerformConnAction($iInitiator, $iContent, $sAction = 'add', $sConnectionType = 'follow')
+    {
+        require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+        if (!$iInitiator || !$iContent)
+            return array();
+
+
+        $oConnections = BxDolConnection::getObjectInstance($sConnectionType === 'follow' ? 'sys_profiles_subscriptions' : 'sys_profiles_friends');
+        $sMethod = "{$sAction}Connection";
+        if (empty($oConnections) || !method_exists($oConnections, $sMethod))
+            return array();
+
+        return array(
+            'result' => (int) $oConnections->$sMethod($iInitiator, $iContent),
+            'content_id' => (int) $iContent,
+            'action' => $sAction
+        );
+    }
+
+    public function serviceProfileConnections($sType = 'friends', $iContentId = 0, $iStart = 0, $iPerPage = 10, $iMutual = false)
+    {
+        $iProfileId = $iContentId ? $iContentId : $this->_iUserId;
+
+        if (!$iProfileId)
+            return array();
+
+        $aMembers = array();
+        $oFriends = BxDolConnection::getObjectInstance('sys_profiles_friends');
+        $oFollowers = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+
+        switch ($sType) {
+            case 'friends':
+                if (!$oFriends)
+                    return array();
+
+                $aMembers = $oFriends->getConnectedContent($iProfileId, $iMutual, $iStart, $iPerPage);
+                break;
+            case 'connections':
+                if (!$oFollowers)
+                    return array();
+
+                $aMembers = $oFollowers->getConnectedContent($iProfileId, $iMutual, $iStart, $iPerPage);
+
+                break;
+            case 'recent':
+            case 'active':
+            case 'online':
+            case 'recommended':
+            case 'top':
+                if (BxDolRequest::serviceExists('bx_persons', 'get_members')) {
+                    $aPersons = BxDolService::call('bx_persons', 'get_members', array($sType, $iStart, $iPerPage));
+                    foreach ($aPersons as &$aPerson)
+                        $aMembers[$aPerson['author']] = $aPerson;
+                }
+                break;
+        }
+
+        if (empty($aMembers))
+            return $aMembers;
+
+        $aRet = array();
+        foreach ($aMembers as $iId => $aInfo) {
+            if (($oProfile = BxDolProfile::getInstance($iId))) {
+                $oAccount = $oProfile->getAccountObject();
+                $aRet[$iId] = array(
+                    'name' => $oProfile->getDisplayName(),
+                    'profile_display_name' => $oProfile->getDisplayName(),
+                    'email' => $oAccount->getEmail(),
+                    'id' => $iId,
+                    'url' => $oProfile->getUrl(),
+                    'info' => $oProfile->getInfo(),
+                    'thumb' => $oProfile->getThumb(),
+                    'picture' => $oProfile->getPicture(),
+                    'avatar' => $oProfile->getAvatar(),
+                    'is_online' => $oProfile->isOnline(),
+                    'cover' => $oProfile->getUnitCover(),
+                    'status' => $oProfile->getStatus(),
+                    'followers' => (int) $oFollowers->getConnectedInitiatorsCount($iId),
+                    'friends' => (int) $oFriends->getConnectedContentCount($iId, true),
+                    'is_friend' => (int) $oFriends->isConnected($iProfileId, $iId, true),
+                    'is_follow' => (int) $oFollowers->isConnected($iProfileId, $iId)
+                );
+            }
+        }
+
+        return $aRet;
+    }
+
+    public function serviceGetModulesList()
+    {
+        $aResult = array();
+        if (!$this->isLogged())
+            return $aResult;
+
+        $aModules = $this->_oDb->getAllCmtsModule();
+        foreach ($aModules as $sKey => $aModule) {
+            if (!$aModule['icon']) {
+                $aResult[$sKey] = array(
+                    'icon' => 'world',
+                    'icon_color' => 'green'
+                );
+                continue;
+            }
+            preg_match('/([a-z]+)(?:-[a-z]+)?\s+col-([a-z]+)/', $aModule['icon'], $aMatches);
+            $aResult[$sKey] = array(
+                'icon' => $aMatches[1],
+                'icon_color' => $aMatches[2]
+            );
+        }
+
+        return $aResult;
+    }
+
+    public function serviceSwitchProfile($iSwitchToProfileId)
+    {
+        $aResult = array('code' => 1);
+
+        if ($iSwitchToProfileId) {
+            $oProfile = BxDolProfile::getInstance($iSwitchToProfileId);
+
+            if ($oProfile && BxDolService::call($oProfile->getModule(), 'act_as_profile')) {
+                $iViewerAccountId = getLoggedId();
+                $iSwitchToAccountId = $oProfile->getAccountId();
+                $bCanSwitch = $iSwitchToAccountId == $iViewerAccountId;
+                bx_alert('account', 'check_switch_context', $iSwitchToAccountId, bx_get_logged_profile_id(), array('switch_to_profile' => $iSwitchToProfileId, 'viewer_account' => $iViewerAccountId, 'override_result' => &$bCanSwitch));
+
+                if ($bCanSwitch) {
+                    $oAccount = BxDolAccount::getInstance();
+                    if ($oAccount->updateProfileContext($iSwitchToProfileId))
+                        $aResult = array('code' => 0, 'msg' => _t('_sys_txt_account_profile_context_changed_success', $oProfile->getDisplayName()));
+                }
+            }
+        }
+
+        return $aResult;
+    }
+
+    public function serviceGetLotInfo($iProfileId, $iLotId, $iJotsLimit = 0, $iStart = BX_IM_EMPTY, $sType = 'new')
+    {
+        $aResult = array('code' => 1);
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        if (!$iLotId || empty($aLotInfo))
+            return $aResult;
+
+        if ($iProfileId !== $this->_iUserId || !$this->_oDb->isParticipant($iLotId, $this->_iUserId))
+            return array('code' => 1, _t('_bx_messenger_not_participant'));
+
+        $CNF = $this->_oConfig->CNF;
+        $iJotsLimit = $iJotsLimit ?: $CNF['MAX_JOTS_LOAD_HISTORY'];
+        $aJotsList = $this->_oDb->getJotsByLotId(['lot' => $iLotId, 'start' => $iStart, 'mode' => $sType, 'limit' => $iJotsLimit]);
+
+        $aJots = array();
+        foreach ($aJotsList as &$aJot) {
+            $aAuthor = BxDolProfile::getInstance($aJot[$CNF['FIELD_MESSAGE_AUTHOR']]);
+            $aAttachments = $this->_oTemplate->getAttachment($aJot);
+            $aJots[] = array_merge($aJot, array(
+                'thumb' => $aAuthor->getThumb(),
+                'name' => $aAuthor->getDisplayName(),
+                'files' => $aAttachments[BX_ATT_GROUPS_ATTACH] ?? ''
+            ));
+        }
+
+        return array('lot_id' => $iLotId, 'jots' => $aJots);
+    }
+
+    public function prepareMessageToDb($sMessage)
+    {
+        if (!$sMessage)
+            return '';
+
+        $aReplacements = [
+            '/\<p>\<br[^>]*>\<\/p>/i' => '<br/>',
+            '/\<p>/i' => '',
+            '/\<\/p>/i' => '<br/>',
+            '/<pre.*>/i' => '<pre>'
+        ];
+
+        $sNewMessage = preg_replace(array_keys($aReplacements), $aReplacements, $sMessage);
+        return preg_replace('/(?:(?:\s*)?<br\/>)+$/', '', $sNewMessage);
+    }
+
+    function actionGetGiphy()
+    {
+        if (!$this->isLogged())
+            return '';
+
+        $aContent = $this->_oTemplate->getGiphyItems(bx_get('action'), urlencode(bx_get('filter')), (float) bx_get('height'), (int) bx_get('start'));
+        if (isset($aContent['content']) && $aContent['content'])
+            return echoJson(array('code' => 0, 'html' => $aContent['content'], 'total' => isset($aContent['pagination']) ? $aContent['pagination']['total_count'] : (int) bx_get('start')));
+
+        return echoJson(array('code' => 1, 'message' => MsgBox(_t('_bx_messenger_giphy_gifs_nothing_found'))));
+    }
+
+    function serviceGetTalksList($aOptions = [])
+    {
+        $mixedGroup = isset($aOptions['group']) ? $aOptions['group'] : '';
+        $iId = isset($aOptions['id']) ? (int) $aOptions['id'] : 0;
+        $iLotId = isset($aOptions['lot']) ? (int) $aOptions['lot'] : 0;
+        $aParams = ['start' => isset($aOptions['count']) ? (int) $aOptions['count'] : 0, 'lot' => $iLotId];
+
+        $CNF = &$this->_oConfig->CNF;
+        $aLotInfo = $iLotId ? $this->_oDb->getLotInfoById($iLotId) : [];
+        if (!empty($aLotInfo) && ($aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_PRIVATE && !$this->_oDb->isParticipant($aLotInfo[$CNF['FIELD_ID']], $this->_iProfileId)))
+            return ['code' => 1, 'message' => _t('_bx_messenger_not_participant')];
+
+        $sTitle = _t('_bx_messenger_nav_menu_item_title_' . ($mixedGroup ? $mixedGroup : 'inbox'));
+        $aLotsList = [];
+        switch ($mixedGroup) {
+            case BX_MSG_TALK_TYPE_THREADS:
+                $aParams['threads'] = true;
+                $aLotsList = $this->_oDb->getMyLots($this->_iProfileId, $aParams);
+                break;
+            case 'public':
+                $aParams['type'] = array(BX_IM_TYPE_SETS, BX_IM_TYPE_PUBLIC);
+                $aLotsList = $this->_oDb->getMyLots($this->_iProfileId, $aParams);
+                break;
+            case BX_MSG_TALK_TYPE_FILES:
+                break;
+            case BX_MSG_TALK_TYPE_MR:
+                $aLotsList = $this->_oDb->getReactionsMentionsLots($this->_iProfileId, $aParams);
+                break;
+            case BX_MSG_TALK_TYPE_REPLIES:
+                $aLotsList = $this->_oDb->getLotsWithReplies($this->_iProfileId, $aParams);
+                break;
+            case BX_MSG_TALK_TYPE_SAVED:
+                $aLotsList = $this->_oDb->getSavedJotInLots($this->_iProfileId, $aParams);
+                break;
+            case BX_MSG_TALK_TYPE_FAVORITES:
+                // Talks marked with a star by the viewer, the same filter as the star toggle on the site.
+                $aLotsList = $this->_oDb->getMyLots($this->_iProfileId, array_merge($aParams, ['star' => true]));
+                break;
+            case BX_MSG_TALK_TYPE_GROUPS:
+                $aParams['group'] = $iId;
+                $aLotsList = $this->_oDb->getMyLots($this->_iProfileId, $aParams);
+                if (empty($aLotsList))
+                    break;
+
+                $aGroupInfo = $this->_oDb->getGroupById($iId);
+                $sTitle = !empty($aGroupInfo) ? $aGroupInfo[$CNF['FMG_NAME']] : current($aLotsList)['title'];
+                break;
+            case BX_MSG_TALK_TYPE_DIRECT:
+                $aLotsList = $this->_oDb->getMyLots($this->_iProfileId, array_merge($aParams, ['type' => BX_IM_TYPE_PRIVATE]));
+                break;
+            default:
+                $aLotsList = $this->_oDb->getMyLots($this->_iProfileId, array_merge($aParams, ['inbox' => true]));
+        }
+
+        $bNext = false;
+        if (isset($aParams['per_page']) && count($aLotsList) === (int) $aParams['per_page'])
+            $bNext = true;
+        else if (count($aLotsList) === $CNF['MAX_LOTS_NUMBER'])
+            $bNext = true;
+
+        return ['title' => $sTitle, 'list' => $aLotsList, 'code' => 0, 'next' => $bNext];
+    }
+
+    function actionGetTalksList()
+    {
+        if (!$this->isLogged())
+            return echoJson(['code' => 1, 'reload' => 1]);
+
+        $aParams = ['group' => bx_get('group'), 'id' => (int) bx_get('id'), 'count' => (int) bx_get('count'), 'lot' => (int) bx_get('lot')];
+
+        $iExcludeConvo = (int) bx_get('exclude_convo');
+        $aData = $this->serviceGetTalksList($aParams);
+        if (isset($aData['code']) && (int) $aData['code'])
+            return echoJson(['code' => 1, 'msg' => $aData['message']]);
+
+        $sTitle = isset($aData['title']) ? $aData['title'] : '';
+        $aLotsList = isset($aData['list']) ? $aData['list'] : [];
+
+        if (!empty($aLotsList)) {
+            if ($iExcludeConvo) {
+                $mixedKey = array_search($iExcludeConvo, array_column($aLotsList, $this->_oConfig->CNF['FIELD_ID']));
+                if ($mixedKey !== false)
+                    unset($aLotsList[$mixedKey]);
+            }
+
+            $sContent = $this->_oTemplate->getLotsPreview($this->_iProfileId, $aLotsList);
+            return echoJson(array(
+                'code' => 0,
+                'html' => $sContent,
+                'title' => _t($sTitle)
+            ));
+        }
+
+        return echoJson(['code' => 1, 'html' => MsgBox(_t('_bx_messenger_txt_msg_no_results')), 'title' => _t($sTitle)]);
+    }
+
+    public function actionGetTopMenuItems()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oLeftMainMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_NAV_LEFT_MENU']);
+        $oLeftGroupsMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_GROUPS_MENU']);
+        return ['top' => $oLeftMainMenu->getMenuItems(), 'groups' => $oLeftGroupsMenu->getMenuItems()];
+    }
+
+    public function serviceGetLotStat($mixedLotId = '')
+    {
+        if (is_numeric($mixedLotId) && (int) $mixedLotId)
+            $aLotInfo = $this->_oDb->getLotInfoById($mixedLotId);
+        else {
+            $sUrl = $mixedLotId ? $this->getPreparedUrl($mixedLotId) : $this->_oConfig->getPageIdent($mixedLotId);
+            $aLotInfo = $this->_oDb->getLotByUrl($sUrl);
+        }
+
+        if (empty($aLotInfo))
+            return $aLotInfo;
+
+        $CNF = &$this->_oConfig->CNF;
+        $aMessages = $this->_oDb->getLotJotsCount($aLotInfo[$CNF['FIELD_ID']]);
+
+        return array(
+            'title' => $aLotInfo[$CNF['FIELD_TITLE']],
+            'created' => $aLotInfo[$CNF['FIELD_ADDED']],
+            'participants' => $aLotInfo[$CNF['FIELD_PARTICIPANTS']] ? explode(',', $aLotInfo[$CNF['FIELD_PARTICIPANTS']]) : array(),
+            'author' => $aLotInfo[$CNF['FIELD_AUTHOR']],
+            'url' => $aLotInfo[$CNF['FIELD_URL']],
+            'type' => $aLotInfo[$CNF['FIELD_TYPE']],
+            'messages' => array_merge($aMessages, array(
+                'total' => array_sum($aMessages)
+            ))
+        );
+    }
+
+    public function serviceGetUnreadLots($iProfileId)
+    {
+        if (!$iProfileId || !($oProfile = BxDolProfile::getInstance($iProfileId)))
+            return array();
+
+        $aLots = $this->_oDb->getLotsWithUnreadMessages($iProfileId);
+        $aLotsInfo = array();
+        foreach ($aLots as $iLotId => $iCount) {
+            $aLotsInfo[$iLotId] = array(
+                'count' => $iCount,
+                'participants' => $this->_oDb->getParticipantsList($iLotId),
+                'new_messages' => $this->_oDb->getUnreadMessages($iProfileId, $iLotId)
+            );
+        }
+
+        return $aLotsInfo;
+    }
+
+    public function actionGetJitsiConferenceForm($iLotId = 0)
+    {
+        if (!$this->_iProfileId) {
+            echo MsgBox(_t('_bx_messenger_jitsi_err_can_join_conference'));
+            exit;
+        }
+
+        header('Content-type: text/html; charset=utf-8');
+
+        if (!$iLotId) {
+            $sUrl = bx_get('url');
+            $sTitle = bx_get('title');
+            $aLot = $this->_oDb->getLotByUrl($sUrl);
+            if (empty($aLot)) {
+                $this->_oDb->createNewLot($this->_iProfileId, array('title' => $sTitle, 'type' => BX_IM_TYPE_PRIVATE, 'url' => $sUrl));
+                $this->onCreateLot($iLotId);
+            } else
+                $iLotId = $aLot[$this->_oConfig->CNF['FIELD_ID']];
+        }
+
+        $CNF = &$this->_oConfig->CNF;
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        $bIsParticipant = $this->_oDb->isParticipant($iLotId, $this->_iProfileId);
+        if (!$bIsParticipant) {
+            if ($aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PRIVATE) {
+                echo MsgBox(_t('_bx_messenger_jitsi_err_can_join_conference'));
+                exit;
+            }
+
+            if (!empty($aLotInfo))
+                $this->_oDb->addMemberToParticipantsList($iLotId, $this->_iProfileId);
+        }
+
+        $aParams['audio_only'] = bx_get('startAudioOnly') ? 1 : 0;
+        $mixedAuthor = $this->_oDb->getActiveJVCItem($iLotId, $CNF['FJVCT_AUTHOR_ID']);
+        if (($mixedAuthor === false || ((int) $mixedAuthor == $this->_iProfileId)) && $this->_oConfig->isJitsiAllowed($aLotInfo[$CNF['FIELD_TYPE']])) {
+            echo $this->_oTemplate->getJitsi($iLotId, $this->_iProfileId, $aParams);
+            exit;
+        }
+
+        $sMessage = _t('_bx_messenger_jitsi_err_cant_type_use');
+        if ($mixedAuthor !== false && ((int) $mixedAuthor != $this->_iProfileId) && ($mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_JOIN_TALK_VC))) {
+            if ($mixedResult !== true)
+                $sMessage = $mixedResult;
+            else {
+                echo $this->_oTemplate->getJitsi($iLotId, $this->_iProfileId, $aParams);
+                exit;
+            }
+        }
+
+        echo MsgBox($sMessage, 2.5);
+        exit;
+    }
+
+    public function onCreateVC($iLotId)
+    {
+        bx_alert($this->_oConfig->getObject('alert'), 'create_vc', $iLotId, $this->_iUserId);
+    }
+
+    public function onJoinVC($iLotId)
+    {
+        $aItem = $this->_oDb->getActiveJVCItem($iLotId);
+        bx_alert($this->_oConfig->getObject('alert'), 'join_vc', $iLotId, $this->_iUserId, array('object_author_id' => $aItem[$this->_oConfig->CNF['FJVCT_AUTHOR_ID']]));
+    }
+
+    public function actionCreateJitsiVideoConference()
+    {
+        $CNF = $this->_oConfig->CNF;
+        $iLotId = (int) bx_get('lot_id');
+        if (!$iLotId)
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_not_found')));
+
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_jitsi_err_can_join_conference')));
+
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        if (empty($aLotInfo))
+            return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_not_found')));
+
+        $bIsParticipant = $this->_oDb->isParticipant($iLotId, $this->_iProfileId);
+        if (!$bIsParticipant) {
+            if ($aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PRIVATE)
+                return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_jitsi_err_can_join_conference')));
+
+            $this->_oDb->addMemberToParticipantsList($iLotId, $this->_iProfileId);
+        }
+
+        $mixedAuthor = $this->_oDb->getActiveJVCItem($iLotId, $CNF['FJVCT_AUTHOR_ID']);
+        if ($mixedAuthor != $this->_iProfileId && ($mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_JOIN_TALK_VC))) {
+            if ($mixedResult !== true)
+                return echoJson(array('code' => 1, 'message' => $mixedResult));
+        }
+
+        $aJVC = $this->_oDb->getJVC($iLotId);
+        $iParticipants = count($this->_oDb->getParticipantsList($iLotId));
+        $aResult = array('code' => 0, 'parts' => $iParticipants);
+        if (!empty($aJVC) && (int) $aJVC[$CNF['FJVC_ACTIVE']]) {
+            if ($this->_oDb->joinToActiveJVC($iLotId, $this->_iProfileId)) {
+                $aResult['jot_id'] = $this->_oDb->getJotIdByJitsiItem((int) $aJVC[$CNF['FJVC_ACTIVE']]);
+                $aResult[$CNF['FJVC_ROOM']] = $aJVC[$CNF['FJVC_ROOM']];
+                $this->onJoinVC($iLotId);
+                return echoJson($aResult);
+            } else
+                $this->_oDb->closeJVC($iLotId);
+        }
+
+        $this->onCreateVC($iLotId);
+        return echoJson(
+            array_merge(
+                array('new' => 1),
+                $aResult,
+                $this->_oDb->createJVC($iLotId, $this->_iProfileId)
+            )
+        );
+    }
+
+    public function actionStopJvc()
+    {
+        $iLotId = (int) bx_get('lot_id');
+        if (!$iLotId)
+            return echoJson(array('code' => 1, 'msg' => _t('_bx_messenger_not_found')));
+
+        if (!$this->_oDb->isParticipant($iLotId, $this->_iProfileId))
+            return echoJson(array('code' => 1));
+
+        if ($this->_oDb->stopJVC($iLotId, $this->_iProfileId))
+            return echoJson(array('code' => 0));
+
+        return echoJson(array('code' => 1));
+    }
+
+    public function actionLeaveJvc()
+    {
+        if (!isLogged())
+            return '';
+
+        $iLotId = (int) bx_get('lot_id');
+        $this->_oDb->leaveJVC($iLotId, $this->_iProfileId);
+    }
+
+    function actionGetTalkFiles()
+    {
+        $iLotId = (int) bx_get('lot_id');
+        $iNumber = (int) bx_get('number');
+
+        $CNF = $this->_oConfig->CNF;
+        if (!$iLotId)
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_found'))));
+
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        if (empty($aLotInfo) || ($aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_PRIVATE && !$this->_oDb->isParticipant($aLotInfo[$CNF['FIELD_ID']], $this->_iProfileId)))
+            return echoJson(array('code' => 1, 'message' => '_bx_messenger_not_participant'));
+
+        if (!($iTotal = $this->_oDb->getLotFilesCount($iLotId)))
+            return echoJson(array('code' => 0, 'html' => MsgBox(_t('_bx_messenger_txt_msg_no_results'))));
+
+        $sContent = $this->_oTemplate->getTalkFiles($this->_iProfileId, $iLotId, $iNumber);
+        return echoJson(array('code' => 0, 'html' => ($iTotal !== $iNumber ? $sContent : ''), 'total' => $iTotal));
+    }
+
+    function actionGetCallPopup()
+    {
+        $iLotId = (int) bx_get('lot');
+        $aResult = array('code' => 1, 'msg' => _t('_bx_messenger_not_found'));
+        if (!$iLotId || !isLogged())
+            return echoJson($aResult);
+
+        if ($this->_oConfig->isAllowedAction(BX_MSG_ACTION_JOIN_TALK_VC) !== true)
+            $aResult = array('code' => 1);
+        else {
+            $mixedContent = $this->_oTemplate->getCallPopup($iLotId, $this->_iProfileId);
+            $aResult = array('code' => +($mixedContent === false), 'popup' => $mixedContent);
+        }
+
+        return echoJson($aResult);
+    }
+
+    function actionGetLotSettings()
+    {
+        $iLotId = (int) bx_get('lot');
+        $aResult = array('code' => 1, 'msg' => _t('_bx_messenger_not_found'));
+        if (!$iLotId || !isLogged())
+            return echoJson($aResult);
+
+        $aResult = array('code' => 1, 'msg' => _t('_bx_messenger_lot_can_change_settings'));
+        $bCheckAction = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true;
+        if (!($this->_oDb->isAuthor($iLotId, $this->_iProfileId) || $bCheckAction))
+            return echoJson($aResult);
+
+        $mixedContent = $this->_oTemplate->getLotSettingsForm($iLotId);
+        $aResult = array(
+            'code' => +($mixedContent === false),
+            'popup' => array(
+                'html' => $mixedContent,
+                'options' => array(
+                    'closeOnOuterClick' => true,
+                    'removeOnClose' => true
+                )
+            )
+        );
+        return echoJson($aResult);
+    }
+
+    function actionSaveLotSettings()
+    {
+        $iLotId = (int) bx_get('lot');
+        $aOptions = bx_get('options');
+        $aResult = ['code' => 1, 'msg' => _t('_bx_messenger_not_found')];
+        if (!$iLotId || !isLogged() || !($aLotInfo = $this->_oDb->getLotInfoById($iLotId)))
+            return echoJson($aResult);
+
+        $aResult = ['code' => 1, 'msg' => _t('_bx_messenger_lot_can_change_settings')];
+        $bCheckAction = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true;
+        if (!($this->_oDb->isAuthor($iLotId, $this->_iProfileId) || $bCheckAction))
+            return echoJson($aResult);
+
+        $aResult = ['code' => 1, 'msg' => _t('_bx_messenger_nothing_has_been_saved')];
+        if ($this->_oDb->saveLotSettings($iLotId, $aOptions))
+            return echoJson(['code' => 0]);
+
+        return echoJson($aResult);
+    }
+
+    function serviceSearch($sText = '', $iStart = 0, $iLimit = 10, $sOrder = 'DESC', $iLotId = 0)
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        if (($iLotId && !isLogged()) || (!isAdmin() && !$iLotId))
+            return _t('_bx_messenger_no_permissions');
+
+        $sResult = $this->_oDb->findInHistory($iLotId, $sText, $iStart, $iLimit, $sOrder);
+        if (empty($sResult))
+            return array();
+
+        $aMessages = array();
+        foreach ($sResult as &$aMessage) {
+            $iType = $aMessage[$CNF['FIELD_TYPE']];
+            $sTitle = $aMessage[$CNF['FIELD_TITLE']] ?
+                _t($aMessage[$CNF['FIELD_TITLE']]) : _t('_bx_messenger_lots_private_lot');
+
+            $aMessages[] = array_merge($aMessage, array(
+                $CNF['FIELD_TITLE'] => $sTitle,
+                'message_url' => $this->_oConfig->getRepostUrl($aMessage['message_id']),
+                $CNF['FIELD_PARTICIPANTS'] => explode(',', $aMessage[$CNF['FIELD_PARTICIPANTS']])
+            ));
+        }
+
+        return $aMessages;
+    }
+
+    function serviceGetVideoConference($sRoomIdent, $iInitiator, $sTitle = '', $sId = '', $aInterfaceConfig = array())
+    {
+        if (!$iInitiator)
+
+            if (!$sRoomIdent)
+                $sRoomIdent = $this->_iProfileId;
+
+        return $this->_oTemplate->getPublicJitsi($iInitiator, $sRoomIdent, $sTitle, $sId, $aInterfaceConfig);
+    }
+
+    function serviceGetPublicRoomInfo($sRoomId)
+    {
+        return $this->_oDb->getPublicVideoRoom($sRoomId);
+    }
+
+    function serviceGenerateRoomName($sIdent)
+    {
+        return $this->_oConfig->getRoomId($sIdent);
+    }
+
+    function actionGetEmojiPicker()
+    {
+        if (!$this->_iProfileId)
+            return;
+
+        echo $this->_oTemplate->getEmojiCode();
+        exit;
+    }
+
+    function actionUpdateUploadedFiles()
+    {
+        $iJotId = bx_get('jot_id');
+        $aFiles = bx_get('files');
+        if (!$this->isLogged())
+            return echoJson(['code' => 1, 'message' => MsgBox(_t('_bx_messenger_not_logged')), 'reload' => 1]);
+
+        if (!$iJotId || empty($aFiles))
+            return echoJson(['code' => 1, 'message' => MsgBox(_t('_bx_messenger_files_can_not_be_uploaded'))]);
+
+        $mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_SEND_MESSAGE, $this->_iProfileId);
+        if ($mixedResult !== true)
+            return echoJson(array('code' => 1, 'message' => $mixedResult));
+
+
+        $CNF = &$this->_oConfig->CNF;
+        $aLotInfo = $this->_oDb->getLotByJotId($iJotId, false);
+        if (
+            empty($aLotInfo) || ($aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_PRIVATE &&
+                !$this->_oDb->isParticipant($aLotInfo[$CNF['FIELD_ID']], $this->_iProfileId))
+        )
+            return echoJson(array('code' => 1, 'message' => '_bx_messenger_not_participant'));
+
+        $aUploadedFiles = $this->_oDb->getJotFiles($iJotId);
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        $aSuccessfulFiles = array();
+        foreach ($aFiles as &$aFile) {
+            $sRealName = $aFile['realname'];
+            if (
+                !empty($aUploadedFiles) && array_filter($aUploadedFiles, function ($aF) use ($sRealName, $CNF) {
+                    return $aF[$CNF['FIELD_ST_NAME']] == $sRealName;
+                })
+            )
+                continue;
+
+            if (!$this->_oConfig->isValidToUpload($aFile['name'])) {
+                $this->_oDb->deleteJot($iJotId, $this->_iProfileId, true);
+                return echoJson(array('code' => 1, 'message' => _t('_bx_messenger_bad_request')));
+            }
+
+            $sFile = BX_DIRECTORY_PATH_TMP . basename($aFile['name']);
+            $sExt = $oStorage->getFileExt($sFile);
+            $iFileId = $oStorage->storeFileFromPath($sFile, $aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PRIVATE, $this->_iProfileId, (int) $iJotId);
+            $sFilename = $oStorage->getFileTitle($sRealName) . ".{$sExt}";
+            if ($iFileId) {
+                $oStorage->afterUploadCleanup($iFileId, $this->_iProfileId);
+                $this->_oDb->updateFiles($iFileId, array(
+                    $CNF['FIELD_ST_JOT'] => $iJotId,
+                    $CNF['FIELD_ST_NAME'] => $sFilename
+                ));
+                $aSuccessfulFiles[] = $sFilename;
+                @unlink($sFile);
+            }
+        }
+
+        if (count($aSuccessfulFiles)) {
+            $aFilesData = [];
+            $aJotInfo = $this->_oDb->getJotById($iJotId);
+            if (!empty($aJotInfo[$CNF['FIELD_MESSAGE_AT']])) {
+                $aFilesData = @unserialize($aJotInfo[$CNF['FIELD_MESSAGE_AT']]);
+
+                if (!is_array($aFilesData))
+                    $aFilesData = [];
+
+                if (isset($aFilesData[BX_ATT_TYPE_FILES_UPLOADING]))
+                    unset($aFilesData[BX_ATT_TYPE_FILES_UPLOADING]);
+            }
+
+            $aStoredFiles = [];
+            if (!empty($aFilesData[BX_ATT_TYPE_FILES])) {
+                if (is_array($aFilesData[BX_ATT_TYPE_FILES]))
+                    $aStoredFiles = $aFilesData[BX_ATT_TYPE_FILES];
+                elseif (is_string($aFilesData[BX_ATT_TYPE_FILES])) {
+                    $aStoredFiles = @unserialize($aFilesData[BX_ATT_TYPE_FILES]);
+                    if (!is_array($aStoredFiles))
+                        $aStoredFiles = [];
+                }
+            }
+
+            $aFilesData[BX_ATT_TYPE_FILES] = $aStoredFiles + $aSuccessfulFiles;
+
+            $this->_oDb->updateJot($iJotId, $CNF['FIELD_MESSAGE_AT'], @serialize($aFilesData));
+        }
+
+        echoJson(array('code' => 0));
+    }
+
+    function serviceGetSearchOptions()
+    {
+        $aResult = array();
+        $CNF = &$this->_oConfig->CNF;
+        foreach ($CNF['SEARCH-CRITERIA'] as &$sItem)
+            $aResult[$sItem] = _t("_bx_messenger_search_item_{$sItem}");
+
+        return $aResult;
+    }
+
+    function serviceSetTalkAvatar($iLotId, $mixedFile)
+    {
+        if (!$mixedFile)
+            return false;
+
+        return $this->setLotAvatar($iLotId, $mixedFile);
+    }
+
+    function setLotAvatar($iLotId, $mixedFile = null)
+    {
+        if (!$this->isLogged() || $mixedFile === null)
+            return false;
+
+        if (!($aLotInfo = $this->_oDb->getLotInfoById($iLotId)))
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        if (is_array($mixedFile) && isset($mixedFile['name'])) {
+            $sFile = BX_DIRECTORY_PATH_TMP . basename($mixedFile['name']);
+            $iFileId = $oStorage->storeFileFromPath($sFile, false, $this->_iProfileId, $iLotId);
+        } elseif (filter_var($mixedFile, FILTER_VALIDATE_URL) !== false)
+            $iFileId = $oStorage->storeFileFromUrl($mixedFile, false, $this->_iProfileId, $iLotId);
+        elseif ((int) $mixedFile)
+            $iFileId = (int) $mixedFile;
+
+        if ($iFileId) {
+            $oStorage->afterUploadCleanup($iFileId, $this->_iProfileId);
+            $this->_oDb->saveLotSettings($iLotId, $iFileId, $CNF['FLS_ICON']);
+        }
+    }
+
+    function actionGetCreateGroupForm()
+    {
+        $iGroupId = (int) bx_get('group');
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'msg' => MsgBox(_t('_bx_messenger_not_logged'))));
+
+        $mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_GROUPS, $this->_iProfileId);
+        if ($mixedResult !== true)
+            return echoJson(array('code' => 1, 'msg' => _t($mixedResult)));
+
+        $aResult = array('code' => 0, 'popup' => $this->_oTemplate->getCreateGroupsForm($this->_iProfileId, $iGroupId));
+        echoJson($aResult);
+    }
+
+    function actionSaveGroup()
+    {
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'msg' => MsgBox(_t('_bx_messenger_not_logged'))));
+
+        $mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_GROUPS, $this->_iProfileId);
+        if ($mixedResult !== true)
+            return echoJson(array('code' => 1, 'msg' => _t($mixedResult)));
+
+        if ($this->_oDb->addGroup($this->_iProfileId, $_POST))
+            return echoJson(array('code' => 0));
+
+        choJson(array('code' => 1, 'msg' => _t('_bx_messenger_nothing_has_been_saved')));
+    }
+
+    function actionLoadGroupsList()
+    {
+        if (!$this->isLogged())
+            return echoJson(array('code' => 1, 'msg' => MsgBox(_t('_bx_messenger_not_logged'))));
+
+        echoJson(array('code' => 0, 'content' => $this->_oTemplate->getNavGroupsMenu($this->_iProfileId)));
+    }
+
+    function serviceIsBlockVersion()
+    {
+        return $this->_isBlockMessenger;
+    }
+
+    function actionAddMembersToGroups()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if (!$this->isLogged())
+            return false;
+
+        $aGroups = $this->_oDb->getAll("SELECT 
+                                            `l`.*, `g`.`{$CNF['FMG_MODULE']}`, `g`.`{$CNF['FMG_NAME']}`, `gl`.`{$CNF['FMGL_GROUP_ID']}`, 
+                                             IF (`l`.`{$CNF['FIELD_UPDATED']}` = 0, `l`.`{$CNF['FIELD_ADDED']}`, `l`.`{$CNF['FIELD_UPDATED']}`) as `order` 
+                                             FROM `{$CNF['TABLE_ENTRIES']}` as `l`
+                                             LEFT JOIN `{$CNF['TABLE_GROUPS_LOTS']}` as `gl` ON `gl`.`{$CNF['FMGL_LOT_ID']}` = `l`.`{$CNF['FIELD_ID']}`
+                                             LEFT JOIN `{$CNF['TABLE_GROUPS']}` as `g` ON `g`.`{$CNF['FMG_ID']}` = `gl`.`{$CNF['FMGL_GROUP_ID']}`
+                                             WHERE `l`.`{$CNF['FIELD_TYPE']}` > 3 
+                                             ORDER BY `g`.`{$CNF['FMG_MODULE']}`"); //AND (`gl`.`{$CNF['FMGL_GROUP_ID']}` IS NULL OR `gl`.`{$CNF['FMGL_GROUP_ID']}` = 0)
+
+        foreach ($aGroups as $aGroup) {
+            if ($aGroup[$CNF['FIELD_URL']])
+                $this->_oDb->registerGroup($aGroup[$CNF['FIELD_URL']], $aGroup[$CNF['FIELD_ID']]);
+        }
+    }
+
+    function actionUpdateGroups()
+    {
+        if (!isLogged())
+            return false;
+
+        $aModules = bx_srv('system', 'get_modules_by_type', ['context']);
+        bx_import('BxDolConnection');
+        foreach ($aModules as &$aModule)
+            if (!bx_srv($aModule['name'], 'act_as_profile')) {
+                $aMyItems = bx_srv($aModule['name'], 'get_participating_profiles', [$this->_iProfileId]);
+                foreach ($aMyItems as &$iItem) {
+                    $oGroupProfile = BxDolProfile::getInstance($iItem);
+                    if ($oGroupProfile) {
+                        $aItemInfo = bx_srv($aModule['name'], 'get_info', array($oGroupProfile->getContentId(), false));
+
+                        $oModule = BxDolModule::getInstance($aModule['name']);
+                        $CNF = &$oModule->_oConfig->CNF;
+                        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+                        if (!$oConnection)
+                            continue;
+
+                        $aFans = $oConnection->getConnectedInitiators($iItem);
+                        if (!empty($aFans)) {
+                            $aObject = BxDolPageQuery::getPageObject($CNF['OBJECT_PAGE_VIEW_ENTRY']);
+                            $sUrl = "i={$aObject['uri']}&id=" . $oGroupProfile->getContentId();
+                            $aLotInfo = $this->_oDb->getLotByUrl($sUrl);
+                            if (empty($aLotInfo)) {
+                                $iLotId = $this->_oDb->createLot($aItemInfo[$CNF['FIELD_AUTHOR']], $sUrl, $aItemInfo[$CNF['FIELD_TITLE']], BX_IM_TYPE_GROUPS, $aFans);
+                                $this->_oDb->registerGroup($sUrl, $iLotId);
+                            }
+                        }
+                    }
+                }
+            }
+    }
+
+    function actionMediaAccordion()
+    {
+        $bShown = (bool) bx_get('hidden');
+        $iFileId = (int) bx_get('id');
+        if (!$this->isLogged() || !$iFileId)
+            return false;
+
+        $oStorage = BxDolStorage::getObjectInstance($this->_oConfig->CNF['OBJECT_STORAGE']);
+        if (!($aFile = $oStorage->getFile($iFileId)))
+            return false;
+
+        $iLotId = $this->_oDb->getLotByJotId($aFile[$this->_oConfig->CNF['FIELD_ST_JOT']]);
+        if (!$this->_oDb->isParticipant($iLotId, $this->_iProfileId))
+            return false;
+
+        if (!$bShown)
+            $this->_oDb->addMediaTrack($iFileId, $this->_iProfileId);
+        else
+            $this->_oDb->removeMediaTracker($iFileId, $this->_iProfileId);
+    }
+
+    function serviceConvertToReply()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $aJots = $this->_oDb->getAll("SELECT * FROM `{$CNF['TABLE_MESSAGES']}` 
+                                     WHERE `{$CNF['FIELD_MESSAGE_AT_TYPE']}`=:type OR `{$CNF['FIELD_MESSAGE_AT']}` LIKE '%\"reply\"%'", array('type' => BX_ATT_TYPE_REPLY));
+        foreach ($aJots as &$aJot) {
+            if ($aJot[$CNF['FIELD_MESSAGE_AT_TYPE']] == BX_ATT_TYPE_REPLY && (int) $aJot[$CNF['FIELD_MESSAGE_AT']])
+                $this->_oDb->query(
+                    "UPDATE `{$CNF['TABLE_MESSAGES']}` SET `{$CNF['FIELD_MESSAGE_REPLY']}`=:reply WHERE `{$CNF['FIELD_MESSAGE_ID']}`=:id",
+                    array('id' => $aJot[$CNF['FIELD_MESSAGE_ID']], 'reply' => (int) $aJot[$CNF['FIELD_MESSAGE_AT']])
+                );
+            elseif (!$aJot[$CNF['FIELD_MESSAGE_AT_TYPE']] && $aJot[$CNF['FIELD_MESSAGE_AT']]) {
+                $aAttachment = @unserialize($aJot[$CNF['FIELD_MESSAGE_AT']]);
+                if (!empty($aAttachment[BX_ATT_TYPE_REPLY]))
+                    $this->_oDb->query("UPDATE `{$CNF['TABLE_MESSAGES']}` SET `{$CNF['FIELD_MESSAGE_REPLY']}`=:reply
+                                WHERE `{$CNF['FIELD_MESSAGE_ID']}`=:id", array('id' => $aJot[$CNF['FIELD_MESSAGE_ID']], 'reply' => $aAttachment[BX_ATT_TYPE_REPLY]));
+            }
+        }
+    }
+
+    function serviceUpdateMemberships()
+    {
+        $aMembershipLevels = BxDolAcl::getInstance()->getMemberships();
+        $sModuleName = $this->getName();
+        $iActionId = BxDolAcl::getInstance()->getMembershipActionId('administrate messages', $sModuleName);
+
+        $iActionEditId = BxDolAcl::getInstance()->getMembershipActionId('edit messages', $sModuleName);
+        $iActionDeleteId = BxDolAcl::getInstance()->getMembershipActionId('delete messages', $sModuleName);
+
+        if (!($iActionId && $iActionEditId && $iActionDeleteId) || empty($aMembershipLevels))
+            return false;
+
+        foreach ($aMembershipLevels as $iMembershipId => $sMembership) {
+            $aAction = BxDolAclQuery::getInstance()->getAction($iMembershipId, $iActionId);
+            $aActionEdit = BxDolAclQuery::getInstance()->getAction($iMembershipId, $iActionEditId);
+            $aActionDelete = BxDolAclQuery::getInstance()->getAction($iMembershipId, $iActionDeleteId);
+
+            if (is_null($aAction['id']) || !is_null($aActionEdit['id']) || !is_null($aActionDelete['id']))
+                continue;
+
+            $this->_oDb->query("INSERT INTO `sys_acl_matrix` SET `IDLevel`=:level, `IDAction`=:action", array('level' => $iMembershipId, 'action' => $iActionDeleteId));
+            $this->_oDb->query("INSERT INTO `sys_acl_matrix` SET `IDLevel`=:level, `IDAction`=:action", array('level' => $iMembershipId, 'action' => $iActionEditId));
+
+            $this->_oDb->query("DELETE FROM `sys_acl_matrix` WHERE `IDLevel`=:level AND `IDAction`=:action", array('level' => $iMembershipId, 'action' => $iActionId));
+        }
+
+        return $iActionId;
+    }
+
+    function servicePublicToPages()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $aLots = $this->_oDb->getAll("SELECT * FROM `{$CNF['TABLE_ENTRIES']}`
+                                               WHERE `{$CNF['FIELD_TYPE']}`!=:type AND `{$CNF['FIELD_URL']}` != ''", array('type' => BX_IM_TYPE_PRIVATE));
+        foreach ($aLots as &$aLot) {
+            $iGroupId = $this->_oDb->getGroupIdByLotId($aLot[$CNF['FIELD_ID']]);
+            if ($iGroupId)
+                continue;
+
+            $sUrl = $aLot[$CNF['FIELD_URL']];
+            if ($sUrl === 'index.php')
+                $sUrl = "i=index";
+            else
+                if (stripos($sUrl, 'i=') === FALSE)
+                    $sUrl = "i=" . str_replace('r=', '', $aLot[$CNF['FIELD_URL']]);
+
+            $aGroup = $this->_oDb->getGroupByUrl($sUrl);
+            if (empty($aGroup))
+                $this->_oDb->registerGroup($sUrl, $aLot[$CNF['FIELD_ID']], true);
+            else
+                $this->_oDb->addLotToGroup($aLot[$CNF['FIELD_ID']], $aGroup[$CNF['FMG_ID']]);
+
+        }
+    }
+
+    public function serviceGetSafeServices()
+    {
+        return [
+            'FindConvo' => 'BxMessengerServices',
+            'LeaveConvo' => 'BxMessengerServices',
+            'DeleteConvo' => 'BxMessengerServices',
+            'ClearConvo' => 'BxMessengerServices',
+            'MuteConvo' => 'BxMessengerServices',
+            'StarConvo' => 'BxMessengerServices',
+            'SaveJot' => 'BxMessengerServices',
+            'SearchConvoMessages' => 'BxMessengerServices',
+            'GetConvoFiles' => 'BxMessengerServices',
+            'GetConvoSettings' => 'BxMessengerServices',
+            'SaveConvoSettings' => 'BxMessengerServices',
+            'GetConvo' => 'BxMessengerServices',
+            'GetConvoUrl' => 'BxMessengerServices',
+            'GetConvosList' => 'BxMessengerServices',
+            'GetConvoMessages' => 'BxMessengerServices',
+            'GetSendForm' => 'BxMessengerServices',
+            'RemoveJot' => 'BxMessengerServices',
+            'SearchUsers' => 'BxMessengerServices',
+            'SearchLots' => 'BxMessengerServices',
+            'GetPartsList' => 'BxMessengerServices',
+            'SavePartsList' => 'BxMessengerServices',
+            'GetBlockContacts' => 'BxMessengerServices',
+
+            //--- Aren't used in App for now.
+            'GetConvoMessage' => '',
+            'GetConferenceInfoByUid' => 'BxMessengerServices',
+            'GetConvoItem' => '',
+            'ClearGhost' => '',
+        ];
+    }
+
+    /*
+     * Should be moved to Services if it's needed, otherwise removed.
+     */
+    public function serviceClearGhost($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+        if (!isset($aOptions['id']))
+            return [];
+
+        $CNF = &$this->_oConfig->CNF;
+        $aJotInfo = $this->_oDb->getJotById($aOptions['id']);
+        if (empty($aJotInfo) || !$this->isAvailable($aJotInfo[$CNF['FIELD_MESSAGE_FK']]))
+            return [];
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+
+        if (!($aFiles = $this->_oDb->getJotFiles($aOptions['id'])))
+            return [];
+
+        $aResultFiles = array_map(function ($aFile) use ($CNF) {
+            return $aFile[$CNF['FIELD_ST_ID']];
+        }, $aFiles);
+
+        $oStorage->afterUploadCleanup($aResultFiles, $this->_iProfileId);
+    }
+
+    /*
+     * Moved to Services
+     */
+    public function serviceSavePartsList($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $aResult = ['code' => 1];
+        if (!$sParams || !isset($aOptions['parts']))
+            return $aResult;
+
+        $aResult = $this->saveParticipantsList($aOptions['parts'], (isset($aOptions['id']) ? $aOptions['id'] : 0));
+        if (isset($aResult['lot'])) {
+            $CNF = &$this->_oConfig->CNF;
+            $aLotInfo = $this->_oDb->getLotInfoById($aResult['lot']);
+            $aItem = $this->_oTemplate->getLotsPreview($this->_iProfileId, [$aLotInfo]);
+            if (!empty($aItem)) {
+                $aItem = current($aItem);
+                $sImageUrl = bx_api_get_relative_url($aItem['bx_if:user']['content']['icon']);
+                $aResult['convo'] = [
+                    'author_data' => (int) $aItem[$CNF['FIELD_AUTHOR']] ? BxDolProfile::getInstance()->getData($aItem[$CNF['FIELD_AUTHOR']]) : [
+                        'id' => 0,
+                        'display_type' => 'unit',
+                        'display_name' => $aItem['bx_if:user']['content']['talk_type'],
+                        'url' => $sImageUrl,
+                        'url_avatar' => $sImageUrl,
+                        'module' => isset($aItem['author_module']) ? $aItem['author_module'] : 'bx_pages',
+                    ],
+                    'title' => $aItem[$CNF['FIELD_TITLE']],
+                    'message' => $aItem['bx_if:user']['content']['message'],
+                    'date' => $aItem['bx_if:timer']['content']['time'],
+                    'id' => $aLotInfo[$CNF['FIELD_HASH']],
+                    'total_messages' => $this->_oDb->getJotsNumber($aItem[$CNF['FIELD_ID']], 0)
+                ];
+
+                $aResult['lot'] = $aLotInfo[$CNF['FIELD_HASH']];
+            }
+        }
+
+        return $aResult;
+    }
+
+    /*
+     * Moved to Services
+     */
+    public function serviceSearchUsers($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+        $aResult = ['code' => 1];
+        $aUsers = [];
+        if (!$sParams || !isset($aOptions['term']))
+            return $aResult;
+
+        $aFoundProfile = $this->searchProfiles($aOptions['term'], isset($aOptions['except']) ? $aOptions['except'] : []);
+        if (!empty($aFoundProfile)) {
+            foreach ($aFoundProfile as &$aProfile) {
+                $oModule = BxDolModule::getInstance($aProfile['module']);
+                $oPCNF = &$oModule->_oConfig->CNF;
+                $aData = $oModule->_oDb->getContentInfoById($aProfile['id']);
+                $oProfile = BxDolProfile::getInstanceByContentAndType($aProfile['id'], $aProfile['module']);
+                $aUsers[] = array_merge($aProfile, [
+                    'id' => $oProfile->id(),
+                    'image' => bx_api_get_image($oPCNF['OBJECT_STORAGE'], $aData[$oPCNF['FIELD_PICTURE']]),
+                    'cover' => bx_api_get_image($oPCNF['OBJECT_STORAGE'], $aData[$oPCNF['FIELD_COVER']])
+                ]);
+            }
+        }
+
+        return $aUsers;
+    }
+
+    /*
+     * Moved to Services.
+     */
+    public function serviceFindConvo($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $aResult = ['code' => 1];
+        if (!isset($aOptions['param']))
+            return $aResult;
+
+        if ($aOptions['param']) {
+            $oProfile = null;
+            $aData = BxDolPageQuery::getSeoLink($this->_oConfig->getName(), 'messenger', ['param_name' => 'profile_id', 'uri' => $aOptions['param']]);
+            if (!empty($aData) && isset($aData['param_value']))
+                $oProfile = BxDolProfile::getInstance($aData['param_value']);
+
+            if (!$oProfile) {
+                $aPersonsData = BxDolPageQuery::getSeoLink('bx_persons', 'view-persons-profile', ['param_name' => 'id', 'uri' => $aOptions['param']]);
+                if (!empty($aPersonsData) && isset($aPersonsData['param_value']))
+                    $oProfile = BxDolProfile::getInstanceByContentAndType($aPersonsData['param_value'], 'bx_persons');
+            }
+
+            if (!$oProfile) {
+                $aOrgData = BxDolPageQuery::getSeoLink('bx_organizations', 'view-organization-profile', ['param_name' => 'id', 'uri' => $aOptions['param']]);
+                if (!empty($aOrgData) && isset($aOrgData['param_value']))
+                    $oProfile = BxDolProfile::getInstanceByContentAndType($aOrgData['param_value'], 'bx_organizations');
+            }
+
+
+            if ($oProfile) {
+                if ($aExistedTalk = $this->_oDb->getLotsByParticipantsList([$oProfile->id(), $this->_iProfileId], BX_IM_TYPE_PRIVATE))
+                    return ['convo' => $aExistedTalk];
+
+                return ['profile' => BxDolProfile::getInstance()->getData($oProfile->id())];
+            }
+        }
+
+        return [];
+    }
+
+    /*
+     * Should be moved to Services if it's needed, otherwise removed.
+     */
+    public function serviceGetConvoItem($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $aResult = array('code' => 1);
+        if (!isset($aOptions['id']) || !($aLotInfo = $this->_oDb->getLotInfoById($aOptions['id'])))
+            return $aResult;
+
+        $iLotId = $aOptions['id'];
+        if (!$this->_oDb->isParticipant($iLotId, $this->_iProfileId))
+            return ['code' => 1, _t('_bx_messenger_not_participant')];
+
+        $CNF = $this->_oConfig->CNF;
+        return array_merge($aLotInfo, ['author_data' => BxDolProfile::getInstance()->getData($aLotInfo[$CNF['FIELD_AUTHOR']])]);
+    }
+
+    /*
+     * Moved to Services
+     */
+    function serviceRemoveJot($sParams = '')
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iJotId = isset($aOptions['jot_id']) ? (int) $aOptions['jot_id'] : 0;
+        $iLotId = isset($aOptions['lot_id']) ? $aOptions['lot_id'] : 0;
+        if (!$iJotId)
+            return [];
+        $this->pusherData('convo_' . $iLotId, ['convo' => $iLotId, 'action' => 'deleted', 'data' => $iJotId]);
+        return $this->serviceDeleteJot($iJotId, true);
+    }
+
+    /*
+     * Moved to Services
+     */
+    function serviceGetConvosList($sParams = '')
+    {
+        $aOptions = json_decode($sParams, true);
+        $aData = $this->serviceGetTalksList($aOptions);
+
+        $aList = $aData['list'];
+        if (isset($aData['code']) && !(int) $aData['code'] && !empty($aData['list']))
+            $aList = $this->_oTemplate->getLotsPreview($this->_iProfileId, $aData['list']);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = [];
+        if (!empty($aList)) {
+            foreach ($aList as $iKey => $aItem) {
+                $sImageUrl = bx_api_get_relative_url($aItem['bx_if:user']['content']['icon']);
+                $aResult[] = [
+                    'author_data' => (int) $aItem[$CNF['FIELD_AUTHOR']] ? BxDolProfile::getInstance()->getData($aItem[$CNF['FIELD_AUTHOR']]) : [
+                        'id' => 0,
+                        'display_type' => 'unit',
+                        'display_name' => $aItem['bx_if:user']['content']['talk_type'],
+                        'url' => $sImageUrl,
+                        'url_avatar' => $sImageUrl,
+                        'module' => isset($aItem['author_module']) ? $aItem['author_module'] : 'bx_pages',
+                    ],
+                    'title' => $aItem[$CNF['FIELD_TITLE']],
+                    'message' => $aItem['bx_if:user']['content']['message'],
+                    'date' => $aItem['bx_if:timer']['content']['time'],
+                    'id' => $aData['list'][$iKey][$CNF['FIELD_HASH']],
+                    'id2' => $aItem[$CNF['FIELD_ID']],
+                    'unread' => $aItem['count'],
+                    'total_messages' => $this->_oDb->getJotsNumber($aItem[$CNF['FIELD_ID']], 0)
+                ];
+            }
+        }
+
+        return $aResult;
+    }
+
+    public function checkAllowedDeleteAnyEntryForProfile($isPerformAction = false, $iProfileId = false)
+    {
+        return true;
+    }
+
+    public function checkAllowedEditAnyEntryForProfile($isPerformAction = false, $iProfileId = false)
+    {
+        return true;
+    }
+
+    /**
+     * Moved to Services
+     */
+    public function unitAPI($iProfileId, $aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if (!$oProfile)
+            return '';
+
+        $sModule = $oProfile->getModule();
+        $iContentId = $oProfile->getContentId();
+        $oModule = BxDolModule::getInstance($sModule);
+
+        $aData = $oModule->_oDb->getContentInfoById($iContentId);
+        $oPCNF = &$oModule->_oConfig->CNF;
+
+        // get profile's url
+        $sUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $oPCNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId));
+
+        $aResult = [
+            'id' => $iContentId,
+            'module' => $sModule,
+            'added' => $aData[$oPCNF['FIELD_ADDED']],
+            'title' => $aData[$oPCNF['FIELD_TITLE']],
+            'url' => bx_api_get_relative_url($sUrl),
+            'image' => bx_api_get_image($oPCNF['OBJECT_STORAGE'], $aData[$oPCNF['FIELD_PICTURE']]),
+            'cover' => bx_api_get_image($oPCNF['OBJECT_STORAGE'], $aData[$oPCNF['FIELD_COVER']])
+        ];
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($oPCNF['OBJECT_PRIVACY_VIEW']);
+        $bPrivacy = $oPrivacy !== false;
+
+        $sKey = 'OBJECT_MENU_SNIPPET_META';
+        if (!empty($CNF[$sKey]) && ($oMetaMenu = BxDolMenu::getObjectInstance($CNF[$sKey], $oModule->_oTemplate)) !== false) {
+            $bPublic = !$bPrivacy || $oPrivacy->check($iContentId) || $oPrivacy->isPartiallyVisible($aData[$CNF['FIELD_ALLOW_VIEW_TO']]);
+
+            $oMetaMenu->setContentModule($sModule);
+            $oMetaMenu->setContentId($iContentId);
+            $oMetaMenu->setContentPublic($bPublic);
+
+            $aResult['meta'] = $oMetaMenu->getCodeAPI();
+        }
+
+        return $aResult;
+    }
+
+    function serviceGetBlockContactsMessenger($mixedParams = [])
+    {
+        if (bx_is_api())
+            return bx_srv($this->getName(), 'get_block_contacts', [$mixedParams], 'Services');
+
+        return $this->_oTemplate->getContacts($this->_iProfileId, $mixedParams);
+    }
+
+    public function serviceGetBroadcastFields($aInputsAdd = array())
+    {
+        $aFields = $this->_oTemplate->_getBroadcastFields();
+
+        $aResult = [];
+        foreach ($aFields as $sName => $aField) {
+            if (isset($aField['skip']) || !isset($aField['caption']))
+                continue;
+
+            $aResult[$sName] = $aField['caption'];
+        }
+
+        return $aResult;
+    }
+
+    private function queueBroadcastDelivery($iConvoId, $iJotId, $iAuthorId, $aNotificationsType = [], $iAfterUserId = 0)
+    {
+        if (!$iConvoId || !$iJotId || !$iAuthorId)
+            return false;
+
+        $sStamp = str_replace('.', '', sprintf('%.6F', microtime(true)));
+        $sJobName = sprintf('%s_broadcast_%d_%d_%s', $this->_oConfig->getName(), $iConvoId, $iAfterUserId, $sStamp);
+
+        return BxDolBackgroundJobs::getInstance()->add($sJobName, [
+            $this->_oConfig->getName(),
+            'process_broadcast_queue',
+            [$iConvoId, $iJotId, $iAuthorId, (array) $aNotificationsType, $iAfterUserId],
+            'Module'
+        ], 5);
+    }
+
+    public function serviceProcessBroadcastQueue($iConvoId, $iJotId, $iAuthorId, $aNotificationsType = [], $iAfterUserId = 0)
+    {
+        if (!$iConvoId || !$iJotId || !$iAuthorId)
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+        $aConvoInfo = $this->_oDb->getLotInfoById($iConvoId);
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+        if (empty($aConvoInfo) || empty($aJotInfo) || (int) $aConvoInfo[$CNF['FIELD_TYPE']] !== BX_IM_TYPE_BROADCAST)
+            return false;
+
+        $sModule = $this->_oConfig->getObject('alert');
+        for ($iBatch = 0; $iBatch < self::BROADCAST_BATCHES_PER_RUN; $iBatch++) {
+            $aParticipants = $this->_oDb->getBroadcastParticipants($iConvoId, self::BROADCAST_BATCH_SIZE + 1, $iAfterUserId);
+            if (empty($aParticipants))
+                return true;
+
+            $bHasMore = count($aParticipants) > self::BROADCAST_BATCH_SIZE;
+            if ($bHasMore)
+                $aParticipants = array_slice($aParticipants, 0, self::BROADCAST_BATCH_SIZE);
+
+            $this->_oDb->markAsNewJot($aParticipants, $iConvoId, $iJotId, false);
+            foreach ($aParticipants as $iPart) {
+                bx_alert($sModule, 'got_broadcast_ntfs', $iConvoId, $iAuthorId, [
+                    'object_author_id' => $iPart,
+                    'recipient_id' => $iPart,
+                    'subobject_id' => $iJotId,
+                    'source' => $sModule . '_' . $iJotId,
+                    'source_mac' => $sModule . '_' . $iAuthorId . '_' . $iJotId,
+                ] + (array) $aNotificationsType);
+            }
+
+            $iAfterUserId = (int) end($aParticipants);
+            if (!$bHasMore)
+                return true;
+        }
+
+        return $this->queueBroadcastDelivery($iConvoId, $iJotId, $iAuthorId, $aNotificationsType, $iAfterUserId);
+    }
+
+    private function getProfilesByCriteria($aData, $aOptions = [])
+    {
+        $bCountOnly = !empty($aOptions['count_only']);
+        $aExcludeProfiles = $aOptions['exclude_profiles'] ?? [];
+        $aIncludeProfiles = $aOptions['include_profiles'] ?? [];
+
+        if (isset($aData['connection_type'])) {
+            if ($oConnection = $this->_oConfig->getConnectionByType($aData['connection_type'])) {
+                if ($bCountOnly && empty($aExcludeProfiles) && empty($aIncludeProfiles))
+                    return (int)$oConnection->getConnectedContentCount($this->_iProfileId, $aData['connection_type'] === 'friends');
+
+                $aProfiles = $oConnection->getConnectedContent($this->_iProfileId, $aData['connection_type'] === 'friends');
+                if (!empty($aExcludeProfiles))
+                    $aProfiles = array_values(array_diff($aProfiles, $aExcludeProfiles));
+
+                if (!empty($aIncludeProfiles))
+                    $aProfiles = array_unique(array_merge($aProfiles, $aIncludeProfiles));
+
+                return $bCountOnly ? count($aProfiles) : $aProfiles;
+            }
+        }
+
+        $CNF = &$this->_oConfig->CNF;
+        if (!($oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_FILTER'], $CNF['OBJECT_FORM_FILTER_DISPLAY'], $this->_oTemplate)))
+            return false;
+
+        $oForm->filteredForm();
+        $aFields = [];
+        foreach ($oForm->aInputs as &$aInput) {
+            if (!isset($aData[$aInput['name']]) || isset($aInput['skip']) || $aInput['name'] === 'convo_type')
+                continue;
+
+            if (!empty($aInput['values']))
+                $aFields[$aInput['name']] = array_filter($aData[$aInput['name']], function ($sVal) use ($aInput) {
+                    return in_array($sVal, array_keys($aInput['values']));
+                });
+            else
+                $aFields[$aInput['name']] = $aData[$aInput['name']];
+        }
+
+        $aParams = [];
+        if (!empty($aExcludeProfiles))
+            $aParams['exclude_profiles'] = $aExcludeProfiles;
+
+        if (!empty($aIncludeProfiles))
+            $aParams['include_profiles'] = $aIncludeProfiles;
+
+        $aProfiles = $this->_oDb->getProfilesByCriteria($aFields, $aParams);
+        bx_alert($this->_oConfig->getObject('alert'), 'broadcast_profiles_list', 0, 0, [
+            'profiles' => &$aProfiles
+        ]);
+
+        if ($bCountOnly)
+            return count($aProfiles);
+
+        return $aProfiles;
+    }
+
+    function actionCalculateProfiles()
+    {
+        $aData = bx_get('data');
+        $aManuall = array_values(array_unique(array_filter(array_map('intval', (array)bx_get('manually')), function ($iProfileId) {
+            return $iProfileId > 0;
+        })));
+
+        $iConvoType = isset($aData['convo_type']) ? $aData['convo_type'] : 0;
+        if (!$this->isLogged() || (empty($aData) && empty($aManuall)) || !$iConvoType)
+            return false;
+
+        $aParams['count_only'] = true;
+        if (!empty($aManuall))
+            $aParams['include_profiles'] = $aManuall;
+
+        $iTotal = (int)$this->getProfilesByCriteria($aData, $aParams);
+        echoJson(['msg' => _t('_bx_messenger_broadcast_total_profiles', $iTotal)]);
+    }
+
+    function serviceGetBroadcastCard($iJotId)
+    {
+        if (!$iJotId)
+            return '';
+
+        $aLotInfo = $this->_oDb->getLotByJotId($iJotId, false);
+        $aJotInfo = $this->_oDb->getJotById($iJotId);
+
+        return $this->_oTemplate->parseHtmlByName('broadcast-message.html', ['title' => $aLotInfo['title'], 'content' => $aJotInfo['message']]);
+    }
+
+    function actionGetFilterCriteria()
+    {
+        if (!$this->isLogged() && $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_BROADCASTS) !== true)
+            return echoJson(['code' => 1]);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $sType = bx_get('type');
+        $sHtml = '';
+        switch ($sType) {
+            case BX_MSG_TALK_TYPE_BROADCAST:
+                $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_FILTER'], $CNF['OBJECT_FORM_FILTER_DISPLAY'], $this->_oTemplate);
+                if ($oForm->filteredForm())
+                    $sHtml = $oForm->getCode();
+                else
+                    $sHtml = MsgBox(_t('_bx_messenger_no_criteria_available'));
+
+                break;
+            case 'followers':
+            case 'friends':
+                $sHtml = $this->_oTemplate->getConnectionsForm($sType, $this->_iProfileId);
+                break;
+            /*default:
+                $aForm = [
+                    'form_attrs' => [
+                        'method' => 'post',
+                        'id' => $CNF['OBJECT_FORM_ENTRY'],
+                        'class' => 'space-y-4 max-h-60 overflow-y-auto'
+                    ],
+                    'inputs' => $this->_oTemplate->getNotificationFormData()
+                ];
+
+                $oForm = new BxTemplFormView($aForm);
+                $sHtml = $oForm -> getCode();*/
+        }
+
+        echoJson(['code' => 0, 'html' => $sHtml]);
+    }
+
+    public function actionAjaxGetRecipients()
+    {
+        $sTerm = bx_get('term');
+        $a = BxDolService::call('system', 'profiles_search', array($sTerm), 'TemplServiceProfiles');
+
+        header('Content-Type:text/javascript; charset=utf-8');
+        echo (json_encode($a));
+    }
+
+    function actionGetTalkInfo()
+    {
+        $iLotId = (int) bx_get('lot_id');
+        if (!$iLotId)
+            return echoJson(array('code' => 1, 'html' => MsgBox(_t('_bx_messenger_not_found'))));
+
+        if (!$this->isAvailable($iLotId))
+            return echoJson(array('code' => 1, 'message' => '_bx_messenger_not_participant'));
+
+        $sContent = $this->_oTemplate->getInfoBlockContent($iLotId);
+        return echoJson(array('code' => 0, 'html' => $sContent));
+    }
+
+    public function broadcastNewJot($iLotId, $iJotId)
+    {
+        $iLotId = (int)$iLotId;
+        $iJotId = (int)$iJotId;
+        if (!$iLotId || !$iJotId)
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        if (empty($aLotInfo) || empty($aLotInfo[$CNF['FIELD_HASH']]))
+            return false;
+
+        $sHash = mb_strtolower($aLotInfo[$CNF['FIELD_HASH']]);
+        // NEO JotItem expects Services API shape (menu.items)
+        bx_import('Services', $this->_aModule);
+        $oServices = new BxMessengerServices();
+        $this->pusherData('convo_' . $sHash, [
+            'convo' => $iLotId,
+            'action' => 'added',
+            'data' => $oServices->serviceGetConvoMessages([
+                'load' => 'ids',
+                'lot' => $sHash,
+                'jot' => $iJotId,
+            ]),
+        ]);
+
+        $aParticipantsList = $this->_oDb->getParticipantsList($iLotId, true);
+        foreach ($aParticipantsList as $iProfile) {
+            $this->pusherData('profile_' . $iProfile, ['convo' => $iLotId]);
+        }
+
+        return true;
+    }
+
+    /**
+     * Moved to Services
+     */
+    private function pusherData($sAction, $aData = [])
+    {
+        $oSockets = BxDolSockets::getInstance();
+
+        if (isset($aData['convo']) && (int) $aData['convo']) {
+            $CNF = &$this->_oConfig->CNF;
+            $aLotInfo = $this->_oDb->getLotInfoById($aData['convo']);
+            $aData['id'] = mb_strtolower($aLotInfo[$CNF['FIELD_HASH']]);
+        }
+
+        $aData['user_id'] = $this->_iProfileId;
+
+        if ($oSockets->isEnabled() && $sAction && !empty($aData)) {
+            $oSockets->sendEvent('bx', 'messenger', $sAction, $aData);
+        }
+    }
+
+}
+
+/** @} */

+ 1169 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerServices.php

@@ -0,0 +1,1169 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup	Messenger Messenger
+ * @ingroup		UnaModules
+ *
+ * @{
+ */
+
+/**
+ * For API only.
+ */
+class BxMessengerServices extends BxDol
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_iProfileId;
+    protected $_bAdministrator = null;
+    protected $_aLotMenuShort = null;
+    protected $_aStarredLots = null;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $this->_sModule = 'bx_messenger';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        $this->_iProfileId = bx_get_logged_profile_id();
+    }
+
+    public function serviceGetBlockMain()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aMenuMain = [];
+        if(($oMenuMain = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_NAV_LEFT_MENU'])) !== false)
+            $aMenuMain =  $oMenuMain->getCodeAPI();
+
+        return [
+            bx_api_get_block('messenger_main_page', [
+                'config' => [
+                    'permissions' => [
+                        'create_talk' => $this->_oModule->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_TALKS, $this->_iProfileId) === true ? 1 : 0,
+                        'send_message' => $this->_oModule->_oConfig->isAllowedAction(BX_MSG_ACTION_SEND_MESSAGE, $this->_iProfileId) === true ? 1 : 0,
+                    ]
+                ],
+                'menu' => $aMenuMain,
+                'form' => ['data' => $this->serviceGetSendForm()],
+            ], [
+                'ext' => [
+                    'name' => $this->_sModule, 
+                    'params' => ($aParams = bx_get('params')) !== false ? $aParams : [],
+                    'request' => ['url' => '/api.php?r=' . $this->_sModule . '/get_send_form/Services', 'immutable' => true]
+                ]
+            ])
+        ];
+    }
+
+    public function serviceGetBlockContacts($mixedParams)
+    {
+        $aParams = bx_api_get_browse_params($mixedParams, true);
+
+        $aBlock = BxDolPage::getBlockProcessing();
+
+        $sContentType = 'browse';
+        if(($sK = 'config_api') && $aBlock[$sK] && is_array($aBlock[$sK]))
+            $sContentType = $aBlock[$sK]['content_type'] ?? $sContentType;
+
+        $bBrowseSimple = $sContentType == 'browse_simple';
+
+        $aProfiles = !defined('BX_API_PAGE') || $bBrowseSimple ? $this->_oModule->_oTemplate->getContacts($this->_iProfileId, $aParams) : [];
+
+        $aData = [];
+        foreach($aProfiles as &$aProfile)
+            if($aProfile['id'] != $this->_iProfileId)
+                $aData[] = $this->_unitProfile($aProfile['id']);
+
+        if(!$bBrowseSimple || $aData)
+            $aData = [bx_api_get_block($sContentType, [
+                'module' => $this->_sModule,
+                'unit' => 'mixed',
+                'request_url' => '/api.php?r=' . $this->_sModule . '/get_block_contacts/Services&params[]=',
+                'data' => $aData,
+                'params' => [
+                    'start' => isset($aParams['start']) ? $aParams['start'] : 0,
+                    'per_page' => isset($aParams['per_page']) ? $aParams['per_page'] : 0 
+                ]
+            ])];
+
+        return $aData;
+    }
+
+    /*
+     * Find convo (lot) related to logged in profile and context if the last one is provided.
+     */
+    public function serviceFindConvo($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $aParticipants = [$this->_iProfileId];
+        if(!empty($aOptions['context']))
+            $aParticipants[] = $aOptions['context'];
+
+        $aLot = $this->_oModule->_oDb->getLotsByParticipantsList($aParticipants, BX_IM_TYPE_PRIVATE);
+        if(empty($aLot) || !is_array($aLot))
+            return [];
+
+        return ['lot' => $aLot];
+    }
+    
+    /**
+     * Leave convo (lot) with defined ID
+     */
+    public function serviceLeaveConvo($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+
+        if(!$iLotId || !$this->_oModule->_oDb->isParticipant($iLotId, $this->_iProfileId))
+            return ['code' => 1, 'message' => _t('_bx_messenger_not_participant')];
+
+        if($this->_oModule->_oDb->isAuthor($iLotId, $this->_iProfileId))
+            return ['code' => 2, 'message' => _t('_bx_messenger_cant_leave')];
+
+        return ['code' => !$this->_oModule->_oDb->leaveLot($iLotId, $this->_iProfileId) ? 3 : 0];
+    }
+
+    /**
+     * Delete convo (lot) with defined ID
+     */
+    public function serviceDeleteConvo($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+
+        if(!$iLotId || !($this->_oModule->_oDb->isAuthor($iLotId, $this->_iProfileId) || ($this->_oModule->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true)))
+            return ['code' => 1, 'message' => _t('_bx_messenger_can_not_delete')];
+
+        return ['code' => !$this->_oModule->_oDb->deleteLot($iLotId) ? 2 : 0];
+    }
+
+    /**
+     * Remove all messages from convo (lot) with defined ID
+     */
+    public function serviceClearConvo($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+
+        if(!$iLotId || !$this->_isAllowedToManage($iLotId)) {
+            return ['code' => 1, 'message' => _t('_bx_messenger_no_permissions')];
+        }
+
+        return ['code' => $this->_oModule->_oDb->clearLot($iLotId) === false ? 2 : 0];
+    }
+
+    /**
+     * Search messages of convo (lot) with defined ID by their text, newest first, by pages
+     */
+    public function serviceSearchConvoMessages($sParams)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+        $sTerm = isset($aOptions['term']) ? trim((string)$aOptions['term']) : '';
+        $iStart = isset($aOptions['start']) ? max(0, (int)$aOptions['start']) : 0;
+        $iPerPage = 20;
+
+        if(!$iLotId || !$this->_iProfileId || !$this->_oModule->_oDb->getLotInfoById($iLotId)) {
+            return ['code' => 1, 'message' => _t('_bx_messenger_not_found')];
+        }
+
+        if(!$this->_oModule->_oDb->isParticipant($iLotId, $this->_iProfileId) && !$this->_isAdministrator()) {
+            return ['code' => 2, 'message' => _t('_bx_messenger_not_participant')];
+        }
+
+        $aResult = ['code' => 0, 'jots' => [], 'params' => ['start' => $iStart, 'per_page' => $iPerPage]];
+        if($sTerm === '') {
+            return $aResult;
+        }
+
+        // LIKE wildcards in the term are searched as plain characters.
+        $sTerm = addcslashes($sTerm, '%_\\');
+
+        $aJots = $this->_oModule->_oDb->findInHistory($iLotId, $sTerm, $iStart, $iPerPage, 'DESC', true);
+        foreach($aJots as $aJot) {
+            $aResult['jots'][] = [
+                'id' => (int)$aJot['message_id'],
+                'message' => $aJot[$CNF['FIELD_MESSAGE']],
+                'added' => (int)$aJot['message_added'],
+                'author_data' => BxDolProfile::getData($aJot['message_author']),
+            ];
+        }
+
+        return $aResult;
+    }
+
+    /**
+     * Save message (jot) with defined ID to the viewer's saved messages, or remove it from there
+     */
+    public function serviceSaveJot($sParams)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aOptions = json_decode($sParams, true);
+
+        $iJotId = isset($aOptions['jot']) ? (int)$aOptions['jot'] : 0;
+
+        if(!$iJotId || !$this->_iProfileId || !($aJotInfo = $this->_oModule->_oDb->getJotById($iJotId))) {
+            return ['code' => 1, 'message' => _t('_bx_messenger_nothing_found')];
+        }
+
+        if(!$this->_isAvailable($aJotInfo[$CNF['FIELD_MESSAGE_FK']])) {
+            return ['code' => 2, 'message' => _t('_bx_messenger_not_participant')];
+        }
+
+        $bSaved = $this->_oModule->_oDb->isJotSaved($iJotId, $this->_iProfileId);
+        $mixedResult = $bSaved ? $this->_oModule->_oDb->deleteSavedJotItems($iJotId, $this->_iProfileId) : $this->_oModule->_oDb->saveJotItem($iJotId, $this->_iProfileId);
+        if($mixedResult === false) {
+            return ['code' => 3, 'message' => _t('_bx_messenger_nothing_has_been_saved')];
+        }
+
+        return ['code' => 0, 'value' => (int)!$bSaved, 'title' => _t($bSaved ? '_bx_messenger_jot_menu_save' : '_bx_messenger_jot_menu_remove_save')];
+    }
+
+    /**
+     * Turn notifications of convo (lot) with defined ID on / off for the viewer
+     */
+    public function serviceMuteConvo($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+
+        if(!$iLotId || !$this->_iProfileId || !$this->_oModule->_oDb->getLotInfoById($iLotId) || !$this->_isAvailable($iLotId)) {
+            return ['code' => 1, 'message' => _t('_bx_messenger_not_found')];
+        }
+
+        $bMuted = (bool)$this->_oModule->_oDb->muteLot($iLotId, $this->_iProfileId);
+
+        return ['code' => 0, 'value' => (int)$bMuted, 'title' => _t($bMuted ? '_bx_messenger_lots_menu_mute_info_on' : '_bx_messenger_lots_menu_mute_info_off')];
+    }
+
+    /**
+     * Mark convo (lot) with defined ID as important (star) / not important for the viewer
+     */
+    public function serviceStarConvo($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+
+        if(!$iLotId || !$this->_iProfileId || !$this->_oModule->_oDb->getLotInfoById($iLotId) || !$this->_isAvailable($iLotId)) {
+            return ['code' => 1, 'message' => _t('_bx_messenger_not_found')];
+        }
+
+        $bStarred = (bool)$this->_oModule->_oDb->starLot($iLotId, $this->_iProfileId);
+
+        return ['code' => 0, 'value' => (int)$bStarred, 'title' => _t(!$bStarred ? '_bx_messenger_lots_menu_star_on' : '_bx_messenger_lots_menu_star_off')];
+    }
+
+    /**
+     * Get files attached to messages of convo (lot) with defined ID, newest first, by pages
+     */
+    public function serviceGetConvoFiles($sParams)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+        $iStart = isset($aOptions['start']) ? max(0, (int)$aOptions['start']) : 0;
+        $iPerPage = (int)$CNF['PARAM_DEFAULT_TALK_FILES_NUM'];
+
+        if(!$iLotId || !$this->_oModule->_oDb->getLotInfoById($iLotId)) {
+            return ['code' => 1, 'message' => _t('_bx_messenger_not_found')];
+        }
+
+        if(!$this->_iProfileId || !$this->_oModule->_oDb->isParticipant($iLotId, $this->_iProfileId)) {
+            return ['code' => 2, 'message' => _t('_bx_messenger_not_participant')];
+        }
+
+        $sStorage = $CNF['OBJECT_STORAGE'];
+        $oStorage = new BxMessengerStorage($sStorage);
+        // File URLs come from the storage engine object, BxMessengerStorage only checks types.
+        $oStorageEngine = BxDolStorage::getObjectInstance($sStorage);
+
+        $aFiles = [];
+        foreach($this->_oModule->_oDb->getLotFiles($iLotId, $iStart, $iPerPage) as $aFile) {
+            $iFileId = (int)$aFile[$CNF['FIELD_ST_ID']];
+            $bImage = $oStorage->isImageFile($aFile[$CNF['FIELD_ST_TYPE']]);
+
+            $aFiles[] = [
+                'id' => $iFileId,
+                'name' => $aFile[$CNF['FIELD_ST_NAME']],
+                'mime_type' => $aFile[$CNF['FIELD_ST_TYPE']],
+                'size' => isset($aFile['size']) ? (int)$aFile['size'] : 0,
+                'added' => (int)$aFile[$CNF['FIELD_ST_ADDED']],
+                'jot_id' => (int)$aFile[$CNF['FIELD_ST_JOT']],
+                'author_data' => BxDolProfile::getData($aFile[$CNF['FIELD_ST_AUTHOR']]),
+                'image' => $bImage ? bx_api_get_image($sStorage, $iFileId) : null,
+                'url' => $oStorageEngine ? $oStorageEngine->getFileUrlById($iFileId) : '',
+            ];
+        }
+
+        return [
+            'code' => 0,
+            'files' => $aFiles,
+            'params' => [
+                'start' => $iStart,
+                'per_page' => $iPerPage,
+                'total' => $this->_oModule->_oDb->getLotFilesCount($iLotId)
+            ]
+        ];
+    }
+
+    /**
+     * Get settings (allowed actions) of convo (lot) with defined ID
+     */
+    public function serviceGetConvoSettings($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+
+        if(!$iLotId || !$this->_oModule->_oDb->getLotInfoById($iLotId)) {
+            return ['code' => 1, 'message' => _t('_bx_messenger_not_found')];
+        }
+
+        if(!$this->_isAllowedToManage($iLotId)) {
+            return ['code' => 2, 'message' => _t('_bx_messenger_lot_can_change_settings')];
+        }
+
+        return array_merge(['code' => 0, 'title' => _t('_bx_messenger_lot_options_title')], $this->_oModule->_oTemplate->getLotSettingsOptions($iLotId));
+    }
+
+    /**
+     * Save settings (allowed actions) of convo (lot) with defined ID
+     */
+    public function serviceSaveConvoSettings($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+
+        if(!$iLotId || !$this->_oModule->_oDb->getLotInfoById($iLotId)) {
+            return ['code' => 1, 'message' => _t('_bx_messenger_not_found')];
+        }
+
+        if(!$this->_isAllowedToManage($iLotId)) {
+            return ['code' => 2, 'message' => _t('_bx_messenger_lot_can_change_settings')];
+        }
+
+        // Only known options can be saved.
+        $aSettings = $this->_oModule->_oTemplate->getLotSettingsOptions($iLotId);
+        $aValues = isset($aOptions['options']) && is_array($aOptions['options']) ? $aOptions['options'] : [];
+        $aValues = array_values(array_intersect(array_column($aSettings['options'], 'key'), $aValues));
+
+        if($this->_oModule->_oDb->saveLotSettings($iLotId, $aValues) === false) {
+            return ['code' => 3, 'message' => _t('_bx_messenger_nothing_has_been_saved')];
+        }
+
+        return ['code' => 0, 'values' => $aValues];
+    }
+
+    /**
+     * Get convo (lot) info with defined ID
+     */
+    public function serviceGetConvo($sParams)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+        $aLotInfo = [];
+
+        if(!$iLotId || !($aLotInfo = $this->_oModule->_oDb->getLotInfoById($iLotId)))
+            return ['code' => 1, 'message' => _t('_bx_messenger_not_found')];
+
+        if(!$this->_isAvailable($iLotId))
+            return ['code' => 2, 'message' => _t('_bx_messenger_not_participant')];
+
+        $aPartList = $this->_oModule->_oDb->getParticipantsList($iLotId);
+        if((int)$aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_BROADCAST)
+            $aPartList = $this->_oModule->_oDb->getBroadcastParticipants($iLotId);
+
+        return ['code' => 0, 'lot' => [
+            'author_data' => BxDolProfile::getData($aLotInfo[$CNF['FIELD_AUTHOR']]),
+            'parts' => count($aPartList),
+            'files' => $this->_oModule->_oDb->getLotFilesCount($iLotId),
+            'messages' => $this->_oModule->_oDb->getJotsNumber($iLotId, 0),
+        ]];
+    }
+
+    /**
+     * Get convo (lot) URL by recipient ID
+     */
+    public function serviceGetConvoUrl($mixedParams)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aOptions = is_array($mixedParams) ? $mixedParams : json_decode($mixedParams, true);
+
+        $aParticipants = [$this->_iProfileId];
+        if(!empty($aOptions['recipient']))
+            $aParticipants[] = $aOptions['recipient'];
+        
+        $aLot = $this->_oModule->_oDb->findLotByParams([
+            $CNF['FIELD_PARTICIPANTS'] => $aParticipants,
+            $CNF['FIELD_TYPE'] => BX_IM_TYPE_PRIVATE
+        ]);
+
+        if(empty($aLot) || !is_array($aLot)) {
+            $aResult = $this->_oModule->saveParticipantsList($aParticipants);
+            if(!isset($aResult['lot']))
+                return false;
+
+            $aLot = $this->_oModule->_oDb->getLotInfoById($aResult['lot']);
+            if(empty($aLot) || !is_array($aLot))
+                return false;
+        }
+
+        return bx_api_get_relative_url(BxDolPermalinks::getInstance()->permalink($CNF['URL_HOME']) . '/inbox/' . $this->_normalizeConvoHash($aLot['hash']));
+    }
+
+    public function serviceGetConvosList($sParams = '')
+    {
+        $aOptions = json_decode($sParams, true);
+        $aData = $this->_oModule->serviceGetTalksList($aOptions);
+
+        $aList = $aData['list'];
+        if(isset($aData['code']) && !(int)$aData['code'] && !empty($aData['list']))
+            $aList = $this->_oModule->_oTemplate->getLotsPreview($this->_iProfileId, $aData['list']);
+
+        $aResult = [];
+        if(empty($aList))
+            return $aResult;
+
+        $this->_prefetchStarredLots($aData['list']);
+        foreach($aList as $iKey => $aItem)
+            $aResult[] = $this->_unitLot($aData['list'][$iKey], $aItem);
+
+        return $aResult;
+    }
+
+    public function serviceGetConvoMessages($sParams)
+    {
+        $aOptions = is_array($sParams) ? $sParams : json_decode($sParams, true);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $iJot = isset($aOptions['jot']) ? (int)$aOptions['jot'] : 0;
+        $iStart = isset($aOptions['start']) ? (int)$aOptions['start'] : 0;
+
+        $iLotId = 0;
+        if(isset($aOptions['lot']) && $aOptions['lot'])
+            $iLotId = $this->_oModule->_oDb->getConvoByHash($aOptions['lot']);
+
+        $sLoad = isset($aOptions['load']) ? $aOptions['load'] : 'prev';
+        $sArea = isset($aOptions['area_type']) ? $aOptions['area_type'] : 'index';
+
+        if ($iLotId && !$this->_isAvailable($iLotId))
+            return ['code' => 1, 'message' => _t('_bx_messenger_talk_is_not_allowed')];
+
+        $sUrl = isset($aOptions['url']) ? $aOptions['url'] : '';
+        if ($sUrl)
+            $sUrl = $this->_oModule->getPreparedUrl($sUrl);
+
+        $isFocused = (bool)bx_get('focus');
+        $iRequestedJot = (int)bx_get('req_jot');
+        $iLastViewedJot = (int)bx_get('last_viewed_jot');
+        $bUpdateHistory = true;
+        $mixedContent = '';
+        $iUnreadJotsNumber = 0;
+        $iLastUnreadJotId = 0;
+        $bAttach = true;
+        $bRemoveSeparator = false;
+        $aParamsBrowse = [];
+        switch ($sLoad) {
+            case 'new':
+                if (!$iJot) {
+                    $iJot = $this->_oModule->_oDb->getFirstUnreadJot($this->_iProfileId, $iLotId);
+                    if ($iJot)
+                        $iJot = $this->_oModule->_oDb->getPrevJot($iLotId, $iJot);
+                    else {
+                        $aLatestJot = $this->_oModule->_oDb->getLatestJot($iLotId);
+                        $iJot = !empty($aLatestJot) ? (int)$aLatestJot[$CNF['FIELD_MESSAGE_ID']] : 0;
+                    }
+                }
+
+                if ($iRequestedJot) {
+                    if ($this->_oModule->_oDb->getJotsNumber($iLotId, $iJot, $iRequestedJot) >= $CNF['MAX_JOTS_LOAD_HISTORY'])
+                        $bUpdateHistory = false;
+                    else if ($isFocused)
+                        $this->_oModule->_oDb->readMessage($iRequestedJot, $this->_iProfileId);
+                }
+
+                if ($iLastViewedJot && $bUpdateHistory && $isFocused) {
+                    $this->_oModule->_oDb->readMessage($iLastViewedJot, $this->_iProfileId);
+                }
+
+            case 'ids':
+                $mixedContent = [$this->_oModule->_oDb->getJotById($iJot)];
+                break;
+
+            case 'prev':
+                $aCriteria = [
+                    'lot' => $iLotId,
+                    'url' => $sUrl,
+                    'start' => $iJot,
+                    'load' => $sLoad,
+                    'start' => $iStart,
+                    'limit' => $CNF['MAX_JOTS_BY_DEFAULT'],
+                    'area' => $sArea,
+                ];
+
+                $iLastUnreadJotId = $iUnreadJotsNumber = 0;
+                $aUnreadInfo = $this->_oModule->_oDb->getNewJots($this->_iProfileId, $iLotId);
+                if (!empty($aUnreadInfo)) {
+                    $iLastUnreadJotId = (int)$aUnreadInfo[$CNF['FIELD_NEW_JOT']];
+                    $iUnreadJotsNumber = (int)$aUnreadInfo[$CNF['FIELD_NEW_UNREAD']];
+                }
+
+                $aParamsBrowse = array_merge($aParamsBrowse, [
+                    'start' => $iStart,
+                    'limit' => $CNF['MAX_JOTS_BY_DEFAULT'],
+                ]);
+                $mixedContent = $this->_oModule->_oDb->getJotsByLotIdApi($aCriteria);
+
+                /**
+                 * Mark as read (delete) notification for the lot while getting jots list.
+                 */
+                $this->_oModule->_oDb->markNotificationAsRead($this->_iProfileId, $iLotId);
+                break;
+        }
+
+        $aResult = [];
+        if (is_array($mixedContent) && $mixedContent){
+            $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_JOT_MENU']);
+
+            $sStorage = $CNF['OBJECT_STORAGE'];
+            $oStorage = new BxMessengerStorage($sStorage);
+
+            // Messages of the page saved by the viewer, one query for the page.
+            $aSavedJots = array_map('intval', $this->_oModule->_oDb->getSavedJotsByIds($this->_iProfileId, array_column($mixedContent, $CNF['FIELD_MESSAGE_ID'])));
+
+            foreach($mixedContent as &$aJot) {
+                $iJotId = $aJot[$CNF['FIELD_MESSAGE_ID']];
+
+                $this->_oModule->_oDb->readMessage($iJotId, $this->_iProfileId);
+
+                $aFiles = [];
+                if ($mixedFiles = $this->_oModule->_oDb->getJotFiles($iJotId))
+                    foreach($mixedFiles as &$aFile) {
+                        if ($oStorage->isImageFile($aFile[$CNF['FIELD_ST_TYPE']]))
+                            $aFiles[] = bx_api_get_image($sStorage, (int)$aFile[$CNF['FIELD_ST_ID']]);
+                    }
+
+                $aReactions = [];
+                if(($oReactions = BxDolVote::getObjectInstance($CNF['OBJECT_JOTS_RVOTES'], $iJotId)) && $oReactions->isEnabled()) {
+                    $aReactionsOptions = [];
+                    $aReactions = $oReactions->getElementApi($aReactionsOptions);
+                }
+
+                $oMenu->setContentId($iJotId);
+
+                $sReplyMessage = '';
+                if(($iReply = (int)$aJot['reply']) != 0) {
+                    $aReply = $this->_oModule->_oDb->getJotById($iReply);
+                    if(!empty($aReply) && is_array($aReply))
+                        $sReplyMessage = $aReply[$CNF['FIELD_MESSAGE']];
+                }
+
+                $aResult[] = array_merge($aJot, [
+                    $CNF['FIELD_MESSAGE_FK'] => isset($aOptions['lot']) ? $aOptions['lot'] : '',
+                    $CNF['FIELD_MESSAGE'] => $aJot[$CNF['FIELD_MESSAGE']],
+                    'author_data' => BxDolProfile::getData($aJot[$CNF['FIELD_MESSAGE_AUTHOR']]),
+                    'reactions' => $aReactions,
+                    'menu' => $oMenu->getCodeAPI(),
+                    'files' => $aFiles,
+                    'reply_message' => $sReplyMessage,
+                    'saved' => in_array((int)$iJotId, $aSavedJots) ? 1 : 0
+                ]);
+            }
+        }
+
+        return [
+            'code' => 0,
+            'jots' => $aResult,
+            'params' => $aParamsBrowse,
+            'unread_jots' => $iUnreadJotsNumber,
+            'last_unread_jot' => $iLastUnreadJotId
+        ];
+    }
+
+    public function serviceGetSendForm($sParams = '')
+    {
+        if (!$this->_oModule->isLogged())
+            return ['code' => 1, 'msg' => _t('_bx_messenger_not_logged')];
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $oForm = BxBaseFormView::getObjectInstance($CNF['OBJECT_API_FORM_NAME'], $CNF['OBJECT_API_FORM_NAME']);
+
+        $aOptions = [];
+        if ($sParams)
+            $aOptions = json_decode($sParams, true);
+
+        if (!empty($aOptions)){
+            if (isset($aOptions['action']) && $aOptions['action'] === 'edit' && isset($aOptions['id']) && (int)$aOptions['id']){
+                $aJotInfo = $this->_oModule->_oDb->getJotById((int)$aOptions['id']);
+                if ($this->_isAvailable($aJotInfo[$CNF['FIELD_MESSAGE_FK']])){
+                    $oForm->aInputs['message_id']['value'] = $aJotInfo[$CNF['FIELD_MESSAGE_ID']];
+                    $oForm->aInputs['action']['value'] = 'edit';
+                    $oForm->aInputs['message']['value'] = $aJotInfo[$CNF['FIELD_MESSAGE']];
+                }
+            }
+        }
+
+        if ($oForm->isSubmittedAndValid()){
+            
+           /*   if (isset($aOptions['convo_id'])){
+                 $oForm->aInputs['id']['value'] = $aOptions['convo_id'];
+            }     */
+            if (isset($aOptions['reply_id'])){
+                 $oForm->aInputs['reply']['value'] = $aOptions['reply_id'];
+            } 
+        
+            $iLotId = 0 ;
+            $mixedLotId = bx_get('id');
+            
+            if (!$mixedLotId){
+                $mixedLotId = $oForm->aInputs['id']['value'] = $aOptions['convo_id'];
+            }
+
+            if ($mixedLotId)
+                $iLotId = !is_numeric($mixedLotId) ? $this->_oModule->_oDb->getConvoByHash($mixedLotId) : (int)$mixedLotId;
+
+            $aLotInfo = $this->_oModule->_oDb->getLotInfoById($iLotId);
+            if (!empty($aLotInfo) && !$this->_isAvailable($iLotId))
+                return ['code' => 1, 'msg' => _t('_bx_messenger_not_participant')];
+
+            $sLotHash = $this->_normalizeConvoHash($aLotInfo['hash']);
+
+            $aData = [
+                'lot' => $iLotId, 
+                'message' => bx_get('message')
+            ];
+
+           /* if(($iReply = bx_get('reply')) !== false)
+                $aData['reply'] = (int)$iReply;*/
+            if (isset($aOptions['reply_id'])){
+                  $aData['reply'] = $aOptions['reply_id'];
+            } 
+
+            if(($mixPayload = bx_get('payload')) !== false && !$aData['lot']) {
+                if(!empty($mixPayload) && ($mixPayload = json_decode($mixPayload, true)) && is_array($mixPayload))
+                    $aData = array_merge($aData, $mixPayload);
+
+                if(isset($aData['participants']) && !in_array($this->_iProfileId, $aData['participants']))
+                    $aData['participants'][] = $this->_iProfileId;
+            }
+
+            if(($mixedFiles = bx_get('files')) !== false && !empty($mixedFiles))
+                $aData['files'] = explode(',', $mixedFiles);
+
+            $iMessageId = bx_get('message_id');
+            $sAction = bx_get('action');
+            if ($iMessageId && $sAction === 'edit') {
+                $aJotInfo = $this->_oModule->_oDb->getJotById($iMessageId);
+                if (empty($aJotInfo))
+                    return ['code' => 1, 'msg' => _t('_Empty')];
+
+                if (!$this->_isAvailable($aJotInfo[$CNF['FIELD_MESSAGE_FK']]))
+                    return ['code' => 1, 'msg' => _t('_bx_messenger_not_participant')];
+
+                $sMessage = $this->_oModule->prepareMessageToDb($aData['message']);
+                $mixedResult = $this->_oModule->_oDb->isAllowedToEditJot($iMessageId, $this->_iProfileId);
+                if ($mixedResult !== true)
+                    return ['code' => 1, 'msg' => $mixedResult];
+
+                if (!empty($aData['files'])) {
+                    $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+                    $aFilesNames = [];
+                    foreach($aData['files'] as &$iFileId) {
+                        $aFile = $oStorage->getFile($iFileId);
+                        if (empty($aFile))
+                            continue;
+
+                        $aFilesNames[] = $aFile[$CNF['FIELD_ST_NAME']];
+                        $this->_oModule->_oDb->updateFiles($iFileId, array(
+                            $CNF['FIELD_ST_JOT'] => $iMessageId,
+                        ));
+                        $oStorage->afterUploadCleanup($iFileId, $this->_iProfileId);
+                    }
+
+                    $aFilesData = [];
+                    if (!empty($aJotInfo[$CNF['FIELD_MESSAGE_AT']]))
+                        $aFilesData = @unserialize($aJotInfo[$CNF['FIELD_MESSAGE_AT']]);
+
+                    if (!empty($aFilesNames))
+                        $aFilesData[BX_ATT_TYPE_FILES] = ( isset($aFilesData[BX_ATT_TYPE_FILES]) ? $aFilesData[BX_ATT_TYPE_FILES] : [] ) + $aFilesNames;
+
+                    $this->_oModule->_oDb->updateJot($iMessageId, $CNF['FIELD_MESSAGE_AT'], @serialize($aFilesData));
+                }
+
+                if ($sMessage && $this->_oModule->_oDb->editJot($iMessageId, $this->_iProfileId, $sMessage)) {
+                    $this->_oModule->onUpdateJot($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $iMessageId, $aJotInfo[$CNF['FIELD_MESSAGE_AUTHOR']]);
+
+                    $this->_pusherData('convo_' . $sLotHash, ['convo' => $iLotId, 'action' => 'edited', 'data' => $this->serviceGetConvoMessages([
+                        'load' => 'ids',
+                        'lot' => $sLotHash, 
+                        'jot' => $iMessageId
+                    ])]);
+
+                    return ['code' => 0, 'jot_id' => $iMessageId];
+                }
+
+                return ['code' => 1];
+            }
+
+            $aResult = $this->_oModule->sendMessage($aData);
+            $bResult = !empty($aResult) && is_array($aResult);
+            // Socket broadcast lives in sendMessage()
+            return $bResult ? array_merge($aResult, []) : ['msg' => $aResult];
+        }
+
+        return $oForm->getCodeAPI();
+    }
+
+    /*
+     * Get participants list by lot ID.
+     */
+    public function serviceGetPartsList($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iLotId = isset($aOptions['lot']) ? (int)$aOptions['lot'] : 0;
+        if(!$iLotId || !($bAllowed = $this->_oModule->_oDb->isAuthor($iLotId, $this->_iProfileId) || $this->_oModule->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true))
+            return ['code' => 1];
+        
+        $aIds = $this->_oModule->_oDb->getParticipantsList($iLotId);
+        if(empty($aIds) || !is_array($aIds))
+            return ['code' => 2];
+
+        $aResult = [];
+        foreach($aIds as $iId)
+            $aResult[] = BxDolProfile::getData($iId);
+        
+        return $aResult;
+    }
+
+    public function serviceSavePartsList($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $aResult = ['code' => 1];
+        if (!$sParams || !isset($aOptions['parts']))
+            return $aResult;
+
+        if(!in_array($this->_iProfileId, $aOptions['parts']))
+            $aOptions['parts'][] = $this->_iProfileId;
+
+        $aResult = $this->_oModule->saveParticipantsList($aOptions['parts'], (isset($aOptions['id']) ? $aOptions['id'] : 0));
+        if(!isset($aResult['lot']))
+            return $aResult;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aLot = $this->_oModule->_oDb->getLotInfoById($aResult['lot']);
+        $aItems = $this->_oModule->_oTemplate->getLotsPreview($this->_iProfileId, [$aLot]);            
+        if(!empty($aItems) && is_array($aItems))
+            $aResult = array_merge($aResult, [
+                'convo' => $this->_unitLot($aLot, current($aItems)),
+                'lot' => $aLot[$CNF['FIELD_HASH']]
+            ]);
+
+        return $aResult;
+    }
+
+    public function serviceSearchUsers($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+        if (!$sParams || !isset($aOptions['term'])) {
+            return ['code' => 1];
+        }
+
+        // Friends only mode: the module's own search among the viewer's friends.
+        if ($this->_oModule->_oConfig->CNF['USE-FRIENDS-ONLY-MODE']) {
+            return $this->_searchUsersByModule($sParams);
+        }
+
+        $sTerm = trim((string)$aOptions['term']);
+        if ($sTerm === '') {
+            return [];
+        }
+
+        $aExcept = isset($aOptions['except']) && is_array($aOptions['except']) ? array_map('intval', $aOptions['except']) : [];
+        $iLimit = 10;
+
+        // Names are searched in each profile module directly (its quick search fields),
+        // so the result doesn't depend on the site's global search settings.
+        $aProfileIds = [];
+        $aModules = BxDolService::call('system', 'get_profiles_modules', [], 'TemplServiceProfiles');
+        foreach ((array)$aModules as $aModule) {
+            if (!BxDolService::call($aModule['name'], 'act_as_profile') || !($oModule = BxDolModule::getInstance($aModule['name']))) {
+                continue;
+            }
+
+            foreach ((array)$oModule->_oDb->searchByTerm($sTerm, $iLimit * 2) as $aRow) {
+                if (!empty($aRow['profile_id'])) {
+                    $aProfileIds[] = (int)$aRow['profile_id'];
+                }
+            }
+        }
+
+        $aUsers = [];
+        foreach (array_unique($aProfileIds) as $iProfileId) {
+            if ($iProfileId == $this->_iProfileId || in_array($iProfileId, $aExcept)) {
+                continue;
+            }
+
+            if (!($oProfile = BxDolProfile::getInstance($iProfileId)) || $oProfile->checkAllowedProfileContact() !== CHECK_ACTION_RESULT_ALLOWED) {
+                continue;
+            }
+
+            $aUsers[] = BxDolProfile::getData($iProfileId);
+            if (count($aUsers) >= $iLimit) {
+                break;
+            }
+        }
+
+        return $aUsers;
+    }
+
+    /**
+     * Users search with the module's search (friends only mode).
+     */
+    protected function _searchUsersByModule($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+        $aResult = ['code' => 1];
+        $aUsers = [];
+        if (!$sParams || !isset($aOptions['term']))
+            return $aResult;
+
+        $aFoundProfile = $this->_oModule->searchProfiles($aOptions['term'], isset($aOptions['except']) ? $aOptions['except'] : []);
+        if (!empty($aFoundProfile)) {
+            foreach($aFoundProfile as &$aProfile) {
+                if(!($oProfile = BxDolProfile::getInstanceByContentAndType($aProfile['id'], $aProfile['module'])) || $oProfile->checkAllowedProfileContact() !== CHECK_ACTION_RESULT_ALLOWED)
+                    continue;
+
+                $aUsers[] = $aProfile['author_data'];
+            }
+        }
+
+        return $aUsers;
+    }
+
+    public function serviceSearchLots($sParams)
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $sTerm = isset($aOptions['term']) ? $aOptions['term'] : '';
+        $iStarred = isset($aOptions['starred']) ? (int)$aOptions['starred'] : 0;
+        // The list section the search runs in: the results stay inside it.
+        $sGroup = isset($aOptions['group']) ? $aOptions['group'] : '';
+
+        $aSearchParams = [
+            'term' => $sTerm,
+            'star' => (bool)$iStarred || $sGroup === BX_MSG_TALK_TYPE_FAVORITES,
+            // The app searches talks by their titles and participants' names, not by messages.
+            'search_criteria' => [BX_SEARCH_CRITERIA_TITLES, BX_SEARCH_CRITERIA_PARTS]
+        ];
+        if($sGroup === BX_MSG_TALK_TYPE_DIRECT) {
+            $aSearchParams['type'] = BX_IM_TYPE_PRIVATE;
+        }
+
+        $aJots = [];
+        $aLots = $this->_oModule->_oDb->getMyLots($this->_iProfileId, $aSearchParams, $aJots);
+        if(empty($aLots) || !is_array($aLots)) {
+            return [];
+        }
+
+        // Saved messages section: only talks with messages saved by the viewer.
+        if($sGroup === BX_MSG_TALK_TYPE_SAVED) {
+            $CNF = &$this->_oModule->_oConfig->CNF;
+            $aSavedLotIds = array_flip($this->_oModule->_oDb->getSavedLotIds($this->_iProfileId));
+            $aLots = array_values(array_filter($aLots, function($aLot) use ($CNF, $aSavedLotIds) {
+                return isset($aSavedLotIds[(int)$aLot[$CNF['FIELD_ID']]]);
+            }));
+            if(empty($aLots)) {
+                return [];
+            }
+        }
+
+        $aResult = [
+            'lots' => [],
+            'search_list' => []
+        ];
+
+        $aItems = $this->_oModule->_oTemplate->getLotsPreview($this->_iProfileId, $aLots);
+        $this->_prefetchStarredLots($aLots);
+        foreach($aItems as $iKey => $aItem)
+            $aResult['lots'][] = $this->_unitLot($aLots[$iKey], $aItem);
+
+        if(!empty($aJots['jots_list']) && is_array($aJots['jots_list'])) {
+            foreach($aJots['jots_list'] as $iJot => $iLot) {
+                if(!isset($aResult['search_list'][$iLot]))
+                    $aResult['search_list'][$iLot] = [
+                        'id' => $iLot,
+                        'jots' => []
+                    ];
+
+                $aResult['search_list'][$iLot]['jots'][] = $iJot;
+            }
+            
+            if(!empty($aResult['search_list']))
+                $aResult['search_list'] = array_values($aResult['search_list']);
+        }
+
+        return $aResult;
+    }
+
+    public function serviceRemoveJot($sParams = '')
+    {
+        $aOptions = json_decode($sParams, true);
+
+        $iJotId = isset($aOptions['jot_id']) ? (int)$aOptions['jot_id'] : 0;
+        $sLotHash = isset($aOptions['lot_id']) ? $aOptions['lot_id'] : 0;
+        if(!$iJotId)
+            return [];
+
+        $iLotId = $this->_oModule->_oDb->getConvoByHash($sLotHash);
+        $this->_pusherData('convo_' . $sLotHash, [
+            'convo' => $iLotId, 
+            'action' => 'deleted', 
+            'data' => $iJotId
+        ]);
+
+        return $this->_oModule->serviceDeleteJot($iJotId, true);
+    }
+
+    protected function _unitProfile($iProfileId, $aParams = [])
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return '';
+
+        $sModule = $oProfile->getModule();
+        $iContentId = $oProfile->getContentId();
+        $oModule = BxDolModule::getInstance($sModule);
+
+        $aData = $oModule->_oDb->getContentInfoById($iContentId);
+        $oPCNF = &$oModule->_oConfig->CNF;
+
+        $aResult = [
+            'id' => $iContentId,
+            'module' => $sModule,
+            'title' => $oProfile->getDisplayName(),
+            'url' => $this->serviceGetConvoUrl(['recipient' => $iProfileId]),
+            'image' => bx_api_get_image($oPCNF['OBJECT_STORAGE'], $aData[$oPCNF['FIELD_PICTURE']]),
+            'cover' => bx_api_get_image($oPCNF['OBJECT_STORAGE'], $aData[$oPCNF['FIELD_COVER']])
+        ];
+
+        $sKey = 'OBJECT_MENU_SNIPPET_META';
+        if(!empty($CNF[$sKey]) && ($oMetaMenu = BxDolMenu::getObjectInstance($CNF[$sKey], $oModule->_oTemplate)) !== false) {
+            $oPrivacy = BxDolPrivacy::getObjectInstance($oPCNF['OBJECT_PRIVACY_VIEW']);
+            $bPrivacy = $oPrivacy !== false;
+
+            $bPublic = !$bPrivacy || $oPrivacy->check($iContentId) || $oPrivacy->isPartiallyVisible($aData[$CNF['FIELD_ALLOW_VIEW_TO']]);
+
+            $oMetaMenu->setContentModule($sModule);
+            $oMetaMenu->setContentId($iContentId);
+            $oMetaMenu->setContentPublic($bPublic);
+
+            $aResult['meta'] = $oMetaMenu->getCodeAPI();
+        }
+
+        return $aResult;
+    }
+
+    protected function _unitLot($aLot, $aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aParticipants = [];
+        if(!empty($aItem[$CNF['FIELD_PARTICIPANTS']])) {
+            $aPartIds = explode(',', $aItem[$CNF['FIELD_PARTICIPANTS']]);
+            foreach($aPartIds as $iPartId) {
+                if(!$iPartId)
+                    continue;
+
+                $aParticipants[] = BxDolProfile::getData($iPartId, ['with_online' => true]);
+            }
+        }
+
+        $sImageUrl = $aItem['bx_if:user']['content']['icon'];
+        if(!empty($sImageUrl) && is_array($sImageUrl))
+            $sImageUrl = !empty($sImageUrl['src']) ? $sImageUrl['src'] : '';
+        if($sImageUrl)
+            $sImageUrl = bx_api_get_relative_url($sImageUrl);
+
+        return [
+            'author_data' => (int)$aItem[$CNF['FIELD_AUTHOR']] ? BxDolProfile::getData($aItem[$CNF['FIELD_AUTHOR']]) : [
+                'id' => 0,
+                'display_type' => 'unit',
+                'display_name' => $aItem['bx_if:user']['content']['talk_type'],
+                'url' => $sImageUrl,
+                'url_avatar' => $sImageUrl,
+                'module' => isset($aItem['author_module']) ? $aItem['author_module'] : 'bx_pages',
+            ],
+            'participants' => $aParticipants,
+            'title' => $aItem[$CNF['FIELD_TITLE']],
+            'message' => $aItem['bx_if:user']['content']['message'],
+            'date' => $aItem['bx_if:timer']['content']['time'],
+            'id' => $this->_normalizeConvoHash($aLot[$CNF['FIELD_HASH']]),
+            'id2' => $aItem[$CNF['FIELD_ID']],
+            // Marked with a star (favorite) by the viewer.
+            'starred' => $this->_isStarredLot((int)$aItem[$CNF['FIELD_ID']]) ? 1 : 0,
+            // Conversation actions (gear menu): the app requests the items on demand with system/get_menu.
+            'menu' => $this->_getLotMenuShort((int)$aItem[$CNF['FIELD_ID']]),
+            'unread' => $aItem['count'],
+            'total_messages' => $this->_oModule->_oDb->getJotsNumber($aItem[$CNF['FIELD_ID']], 0)
+        ];
+    }
+
+    protected function _pusherData($sAction, $aData = [])
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aData['user_id'] = $this->_iProfileId;
+        if(!empty($aData['convo']) && ($aLotInfo = $this->_oModule->_oDb->getLotInfoById($aData['convo'])))
+            $aData['id'] = $this->_normalizeConvoHash($aLotInfo[$CNF['FIELD_HASH']]);
+
+        if(($oSockets = BxDolSockets::getInstance()) && $oSockets->isEnabled() && $sAction && !empty($aData))
+            $oSockets->sendEvent('bx', 'messenger', $sAction, $aData);
+    }
+
+    /**
+     * Talks of a list marked with a star by the viewer, loaded with one query for the whole list.
+     */
+    protected function _prefetchStarredLots($aLots)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aLotIds = [];
+        foreach((array)$aLots as $aLot) {
+            if(!empty($aLot[$CNF['FIELD_ID']])) {
+                $aLotIds[] = (int)$aLot[$CNF['FIELD_ID']];
+            }
+        }
+
+        $this->_aStarredLots = array_map('intval', $this->_oModule->_oDb->getStarredLots($this->_iProfileId, $aLotIds));
+    }
+
+    protected function _isStarredLot($iLotId)
+    {
+        if($this->_aStarredLots !== null) {
+            return in_array((int)$iLotId, $this->_aStarredLots);
+        }
+
+        return (bool)$this->_oModule->_oDb->isStarred($iLotId, $this->_iProfileId);
+    }
+
+    /**
+     * Short code of the talk actions menu (object, title, params), items are loaded on demand.
+     */
+    protected function _getLotMenuShort($iLotId)
+    {
+        if($this->_aLotMenuShort === null) {
+            $CNF = &$this->_oModule->_oConfig->CNF;
+
+            $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_TALK_INFO_MENU']);
+            $this->_aLotMenuShort = $oMenu ? $oMenu->getShortCodeAPI() : [];
+        }
+
+        if(empty($this->_aLotMenuShort)) {
+            return [];
+        }
+
+        return array_merge($this->_aLotMenuShort, ['params' => ['lot' => (int)$iLotId]]);
+    }
+
+    /**
+     * Talk author or a member who can administrate talks.
+     */
+    protected function _isAllowedToManage($iLotId)
+    {
+        return $this->_iProfileId && ($this->_oModule->_oDb->isAuthor($iLotId, $this->_iProfileId) || $this->_isAdministrator());
+    }
+
+    /**
+     * Member can administrate talks. Checked once per request: the ACL check counts the action.
+     */
+    protected function _isAdministrator()
+    {
+        if($this->_bAdministrator === null) {
+            $this->_bAdministrator = $this->_iProfileId && $this->_oModule->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $this->_iProfileId) === true;
+        }
+
+        return $this->_bAdministrator;
+    }
+
+    protected function _isAvailable($iLotId)
+    {
+        if(!$iLotId)
+            return false;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aLotInfo = $this->_oModule->_oDb->getLotInfoById($iLotId);
+        if(!empty($aLotInfo) && !$this->_oModule->_oDb->isParticipant($iLotId, $this->_iProfileId) && $aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PRIVATE)
+            return false;
+
+        return true;
+    }
+
+    protected function _normalizeConvoHash($sHash)
+    {
+        return mb_strtolower($sHash);
+    }
+
+    public function log($mixedContents, $sSection = '', $sTitle = '')
+    {
+        if(is_array($mixedContents))
+            $mixedContents = var_export($mixedContents, true);	
+        else if(is_object($mixedContents))
+            $mixedContents = json_encode($mixedContents);
+
+        if(empty($sSection))
+            $sSection = "Core";
+
+        $sTitle .= "\n";
+
+        bx_log($this->_sModule, ":\n[" . $sSection . "] " . $sTitle . $mixedContents);
+    }
+
+    /**
+     * Service allows to get information about conference by room's uid
+     *
+     * @param $sUid
+     * @param $iLimit
+     * @return mixed
+     */
+    public function serviceGetConferenceInfoByUid($sParams){
+        $aOptions = json_decode($sParams, true);
+
+        $sUid = $aOptions['room_id'] ?? '';
+        if (!$sUid)
+            return [];
+
+        $iLimit = $aOptions['limit'] ?? 0;
+        return $this->_oModule->_oDb->getVideoConferenceInfoByRoom($sUid, $iLimit);
+    }
+}
+
+/** @} */

+ 2979 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/classes/BxMessengerTemplate.php

@@ -0,0 +1,2979 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup	Messenger Messenger
+ * @ingroup		UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Module  representation
+ */
+class BxMessengerTemplate extends BxBaseModGeneralTemplate
+{
+	function __construct(&$oConfig, &$oDb)
+	{
+		parent::__construct($oConfig, $oDb);
+	}
+	
+	/**
+	* Attach js and css files for messenger depends on page with messenger block
+	*@param string $sMode 
+	*/
+	public function loadCssJs($sMode = 'all'){
+	    $aCss = [
+				   'main.css',
+                   'video-conference.css',
+                   'video-recorder.css',
+                   'emoji.css',
+                   'messenger-phone.css',
+                   'quill.bubble.css',
+                   'menu-accordion.css',
+                   'lot-briefs.css',
+                   'text-area.css',
+                   'menu-column.css',
+                   'history.css',
+                   'history-header.css',
+                   'message.css',
+                   'talk.css',
+                   'talks-list.css',
+                   'info-block.css',
+                   'time-divider.css',
+                   'message-menu.css',
+                   'giphy.css',
+                   'tailwind-messenger.css',
+                   'create-list.css',
+                   'attachment.css',
+                   'scroll-elements.css',
+                   '3rd-libs.css',
+                   'talk-info.css'
+				];
+
+		$aJs = [
+                    'primus.js',
+                    'record-video.js',
+                    'editor.js',
+                    'storage.js',
+                    'connect.js',
+                    'status.js',
+                    'lazy-loading.js',
+                    'selectors.js',
+                    'jot-menu.js',
+                    'giphy.js',
+                    'messenger.js',
+                    'RecordRTC.min.js',
+                    'adapter.js',
+                    'soundjs.min.js',
+                    'quill.min.js',
+                    'nav-menu.js',
+                    'utils.js',
+                    'media-accordion.js',
+                    'notification-bubbles.js',
+                    'jquery-ui/jquery.ui.widget.min.js',
+                    'jquery-ui/jquery.ui.tooltip.min.js',
+                    'emoji.js',
+                    'create-convo-menu.js'
+				];
+
+
+		if ($sMode === 'all')
+			array_push($aCss, 'messenger.css');
+
+		if ($sMode === 'view')
+			array_push($aCss, 'talk-block.css');
+
+        if ($this->_oConfig->CNF['USE_MENTIONS']) {
+            array_push($aCss, 'quill.mention.css');
+            array_push($aJs, 'quill.mention.min.js');
+        }
+
+        $this->initFilesUploader();
+
+        $this->addCss($aCss);
+		$this->addJs($aJs); 
+	}
+
+	/**
+	* Main function to build post messages area with messages history
+	*@param int $iProfileId logged member id
+	*@param int $iLotId id of conversation. It can be empty if new talk
+	*@param int $iJotId jot id, allows to load history from jot's position
+	*@return string html code
+	*/
+	public function getHistory($iProfileId, $iLotId = BX_IM_EMPTY, $iJotId = BX_IM_EMPTY){
+		$CNF = $this->_oConfig->CNF;
+		$aParams = [
+			'content' => $this -> parseHtmlByName('history-block.html', array(
+                'content' => MsgBox(_t('_bx_messenger_empty_history'))
+            )),
+            'info' => $this -> parseHtmlByName('info-area.html', []),
+            'new_msg_active' => 'none',
+            'unread_count' => 0
+		];
+
+        if ($iLotId){
+            $iUnreadLotsJots = $this->_oDb->getUnreadJotsMessagesCount($iProfileId, $iLotId);
+            if ($iUnreadLotsJots && !$iJotId)
+                $iJotId = $this -> _oDb -> getFirstUnreadJot($iProfileId, $iLotId);
+
+            $sContent = $this->getHistoryArea(['profile_id' => $iProfileId, 'lot' => $iLotId, 'jot' => $iJotId],
+                                               $iUnreadLotsJots && $iUnreadLotsJots < (int)($CNF['MAX_JOTS_BY_DEFAULT']/2));
+            $aParams = [
+			    'content' => $sContent,
+                'new_msg_active' => $iUnreadLotsJots ? 'block' : 'none',
+                'unread_count' => $iUnreadLotsJots,
+                'info' => $this -> parseHtmlByName('info-area.html', []),
+            ];
+		}
+
+        $aParams['bx_if:search'] = array(
+            'condition' => $this->_oConfig->isSearchCriteria(BX_SEARCH_CRITERIA_CONTENT),
+            'content' => []
+        );
+
+		return bx_is_api() ? $aParams : $this -> parseHtmlByName('history.html', $aParams);
+	}
+
+    public function getHistoryArea($aData, $bRead = false, $bEmptyMessage = false){
+        $CNF = $this->_oConfig->CNF;
+        $iProfileId = isset($aData['profile_id']) ? (int)$aData['profile_id']: BX_IM_EMPTY;
+        $iLotId = isset($aData['lot']) ? (int)$aData['lot']: BX_IM_EMPTY;
+        $iJotId = isset($aData['jot']) ? (int)$aData['jot']: BX_IM_EMPTY;
+        $sArea = isset($aData['area']) ? $aData['area']: '';
+
+        $sContent = !$bEmptyMessage ? MsgBox(_t('_bx_messenger_empty_history')) : '';
+        if ($iLotId){
+            $aJots = $this -> getJotsOfLot($iProfileId,
+                array(
+                        'lot_id' => $iLotId,
+                        'limit' => $CNF['MAX_JOTS_BY_DEFAULT'],
+                        'start' => $iJotId,
+                        'display' => true,
+                        'select' => true,
+                        'views' => true,
+                        'read' => $bRead,
+                        'area' => $sArea
+                    ));
+
+            if (!empty($aJots) && isset($aJots['content']))
+                $sContent = $aJots['content'];
+        }
+
+        if (bx_is_api())
+            return $aJots;
+
+        return $this -> parseHtmlByName('history-block.html', array(
+            'content' => $sContent
+        ));
+    }
+
+	public function getTextArea($iProfileId, $iLotId = 0, $bHideTopics = false){
+	    $CNF = $this->_oConfig->CNF;
+
+        $mixedResult = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_SEND_MESSAGE, $iProfileId);
+	    if (!$iProfileId || $mixedResult !== true)
+	        return '';
+
+	    $bRecorder = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_VIDEO_RECORDER, $iProfileId) === true;
+        $bFiles = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_SEND_FIELS, $iProfileId) === true;
+        $bGiphy = $CNF['GIPHY']['api_key'] !== '';
+        $bSmiles = true;
+
+        $bCheckAction = $this->_oConfig->isAllowedAction(BX_MSG_ACTION_ADMINISTRATE_TALKS, $iProfileId) === true;
+        $bIsAuthor = $bCheckAction || $this->_oDb->isAuthor($iLotId, $iProfileId);
+
+        $mixedOptions = $this->_oDb->getLotSettings($iLotId);
+        if ($mixedOptions !== FALSE && is_array($mixedOptions) && !$bIsAuthor) {
+            if (!in_array(BX_MSG_SETTING_MSG, $mixedOptions))
+               return '';
+
+           $bFiles &= in_array(BX_MSG_SETTING_FILES, $mixedOptions);
+           $bRecorder &= in_array(BX_MSG_SETTING_VIDEO_RECORD, $mixedOptions);
+           $bGiphy &= in_array(BX_MSG_SETTING_GIPHY, $mixedOptions);
+           $bSmiles = in_array(BX_MSG_SETTING_SMILES, $mixedOptions);
+        }
+
+	    $aVars = [
+              'bx_if:giphy' => [
+                  'condition' => $bGiphy,
+                  'content' => []
+              ],
+              'bx_if:recording' => [
+                  'condition' => $bRecorder,
+                  'content' => []
+              ],
+              'bx_if:files' => [
+                  'condition' => $bFiles,
+                  'content' => []
+              ],
+              'bx_if:smiles' => [
+                  'condition' => $bSmiles,
+                  'content' => []
+              ],
+              'bx_if:text' => [
+                  'condition' => true,
+                  'content' => []
+              ],
+              'giphy' => $bGiphy ? $this->getGiphyPanel() : '',
+              'bx_if:topics' => [
+                  'condition' => !$iLotId && !$bHideTopics,
+                  'content'  => []
+              ]
+         ];
+
+        $sTextArea = $this -> parseHtmlByName('text-area.html', $aVars);
+        bx_alert($this->_oConfig->getObject('alert'), 'text_area_before', $iLotId, $iProfileId, [
+            'content' => &$sTextArea
+        ]);
+
+        return $sTextArea;
+    }
+
+	public function initFilesUploader(){
+        $this->addCss(array(
+            'filepond-custom.css',
+            'filepond-plugin-media-preview.min.css',
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'filepond/|filepond.min.css',
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'filepond/|filepond-plugin-image-preview.min.css'
+        ));
+
+        $this->addJs(array(
+            'uploader.js',
+            'filepond/filepond.min.js',
+            'filepond/filepond-plugin-image-preview.min.js',
+            'filepond/filepond-plugin-file-validate-size.min.js',
+            'filepond-plugin-media-preview.min.js',
+            'filepond-plugin-file-rename.min.js'
+        ));
+
+        $this->addJsTranslation(array(
+            '_bx_messenger_upload_delete',
+            '_bx_messenger_delete_confirm',
+            '_bx_messenger_upload_invalid_file_type',
+            '_bx_messenger_max_files_upload_error',
+            '_bx_messenger_upload_is_complete',
+            '_bx_messenger_upload_cancelled',
+            '_bx_messenger_uploading_file',
+            '_bx_messenger_invalid_server_response',
+            '_bx_messenger_uploading_remove_button',
+			'_bx_messenger_file_is_too_large_error',
+            '_bx_messenger_file_is_too_large_error_details',
+			'_bx_messenger_file_type_is_not_allowed',
+            '_bx_messenger_empty_history',
+            '_bx_messenger_create_talk_confirm'
+        ));
+    }
+
+    private function getJotMenuCode($iJotId){
+	    $CNF = &$this->_oConfig->CNF;
+
+        $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_JOT_MENU']);
+        $oMenu->setContentId($iJotId);
+
+        foreach($CNF['JOT-MENU-TO-SHOW'] as &$sName)
+            $aItems['bx_repeat:items'][] = $oMenu->getMenuItemByName($sName);
+
+        $aItems['jot_menu'] = $oMenu->getCode();
+        return $this->parseHtmlByName('jot-menu.html', $aItems);
+    }
+
+    private function getFileMenuCode($iFileId, $bAllowedDelete){
+        $CNF = &$this->_oConfig->CNF;
+
+        $oStorage = new BxMessengerStorage($CNF['OBJECT_STORAGE']);
+        $aFile = $oStorage->getFile($iFileId);
+        $aMenuItems = [
+                        [
+                            'onclick' => "{$CNF['JSMain']}.downloadFile({$iFileId})",
+                            'href' => $this->_oConfig->getBaseUri() . "download_file/{$iFileId}",
+                            'attrs' => 'download="' . $aFile[$CNF['FIELD_ST_NAME']] . '"',
+                            'title' => _t('_bx_messenger_file_download'),
+                            'icon' => 'download'
+                        ],
+                        [
+                            'permissions' => true,
+                            'href' => 'javascript:void(0)',
+                            'attrs' => '',
+                            'onclick' => "{$CNF['JSMain']}.removeFile(this, {$iFileId})",
+                            'title' => _t('_bx_messenger_upload_delete'),
+                            'icon' => 'trash'
+                        ],
+                      ];
+
+        $aVars = [];
+        foreach ($aMenuItems as &$aItem) {
+            if (isset($aItem['permissions']) && !$bAllowedDelete)
+                continue;
+
+            $aVars['bx_repeat:menu'][] = $aItem;
+        }
+
+        return $this->parseHtmlByName('file-menu.html', $aVars);
+    }
+
+    /**
+     * Returns right side file's menu in talk history. Allows to  remove or download the file
+     *@param int $iFileId file id in storage table
+     *@param boolean $bIsDeleteAllowed is the vendor of the file
+     *@return string html
+     */
+    public function getFileMenu($iFileId, $bIsDeleteAllowed = false){
+        return $this -> getFileMenuCode($iFileId, $bIsDeleteAllowed);
+    }
+
+    /**
+     * Main function to build post message block for any page
+     * @param int $iProfileId logged member id
+     * @param int $iLotId id of conversation. It can be empty if new talk
+     * @param int $iJotId id of the message in history
+     * @param bool $bIsBlockVersion
+     * @return array content and title of the block
+     */
+	public function getTalkBlock($iProfileId, $iLotId = BX_IM_EMPTY, $iJotId = BX_IM_EMPTY, $bIsBlockVersion = false){
+        $aData = [
+                    'header' => $this->getTalkHeader($iLotId, $iProfileId, $bIsBlockVersion),
+                    'history' => $this->getHistory($iProfileId, $iLotId, $iJotId)
+                ];
+
+	    return bx_is_api() ? $aData : $this -> parseHtmlByName('talk.html', array(
+			'header' => $this->getTalkHeader($iLotId, $iProfileId, $bIsBlockVersion),
+            'top_area'=> $this->getHistoryTopArea(),
+			'history' => $this->getHistory($iProfileId, $iLotId, $iJotId),
+            'text_area' => $this->getTextArea($iProfileId, $iLotId)
+		));
+	}
+
+    public function getTalkBlockByUserName($iViewer, $iProfileId){
+        return $this -> parseHtmlByName('talk.html', array(
+            'header' => $this->getTalkHeaderForUsername($iViewer, $iProfileId),
+            'top_area'=> $this->getHistoryTopArea(),
+            'history' => $this-> getHistory($iViewer),
+            'text_area' => $this->getTextArea($iViewer)
+        ));
+    }
+
+	public function getTalkHeaderForUsername($iViewer, $iProfileId, $bWrap = true){
+	    $oViewer = $this -> getObjectUser($iViewer);
+        $oProfile = $this -> getObjectUser($iProfileId);
+        if (!$oProfile || !$oViewer)
+            return '';
+
+	    return $bWrap ? $this -> parseHtmlByName('talk-header.html', array(
+            'buttons' => '',
+            'back_title' => bx_js_string(_t('_bx_messenger_lots_menu_back_title')),
+            'title' => $this->getThumbsWithUsernames($iProfileId),
+			'menu_button' => $this -> parseHtmlByName('mobile-menu-button.html', array())
+        )): $this->getThumbsWithUsernames($iProfileId);
+    }
+
+    public function getThumbsWithUsernames($mixedProfiles){
+	    if (empty($mixedProfiles))
+	        return '';
+
+	    $aProfiles = $aVars = array();
+	    if (!is_array($mixedProfiles))
+            $aProfiles[] = (int)$mixedProfiles;
+	    else
+            $aProfiles = $mixedProfiles;
+
+	    foreach($aProfiles as &$iProfileId){
+           $oProfile = $this -> getObjectUser($iProfileId);
+           if (!$oProfile)
+                continue;
+
+           $sThumb = $oProfile->getThumb();
+           $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+           $sDisplayName = $oProfile->getDisplayName();
+
+           $aVars['bx_repeat:usernames'][] = array(
+                'username' => $sDisplayName,
+                'bx_if:avatars' => array(
+                    'condition' => $bThumb,
+                    'content' => array(
+                        'thumb' => $sThumb,
+                        'title' => $sDisplayName,
+                    )
+                ),
+                'bx_if:letters' => array(
+                    'condition' => !$bThumb,
+                    'content' => array(
+                        'color' => implode(', ', BxDolTemplate::getColorCode($iProfileId, 1.0)),
+                        'title' => $sDisplayName,
+                        'letter' => mb_substr($sDisplayName, 0, 1)
+                    )
+                )
+           );
+        }
+
+        return $this -> parseHtmlByName('thumb-usernames.html', $aVars);
+    }
+
+    /**
+     * @param integer $iLotId Talk's id
+     * @param integer $iProfileId Viewer id
+     * @param bool $bIsBlockVersion indicates if it is Block Messenger version
+     * @return false|string
+     */
+
+	public function getTalkHeader($iLotId, $iProfileId, $bIsBlockVersion = false, $isArray = false){
+	    $CNF = &$this->_oConfig->CNF;
+        $aLotInfo = array();
+        if ($iLotId)
+            $aLotInfo = $this -> _oDb -> getLotInfoById($iLotId);
+
+        $sTitle = _t('_bx_messenger_page_block_title');
+        if (!empty($aLotInfo))
+        {
+            if ($aLotInfo[$CNF['FIELD_TITLE']])
+                $sTitle = $aLotInfo[$CNF['FIELD_TITLE']];
+
+            if (!$bIsBlockVersion && $this->_oDb->isLinkedTitle($aLotInfo[$CNF['FIELD_TYPE']]))
+                $sTitle = _t('_bx_messenger_linked_title', '<a href ="' . $this->_oConfig->getPageLink($aLotInfo[$CNF['FIELD_URL']]) . '">' . $sTitle . '</a>');
+            else if (!empty($aLotInfo[$CNF['FIELD_TITLE']]))
+                $sTitle = _t($aLotInfo[$CNF['FIELD_TITLE']]);
+            else if ($aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_PRIVATE)
+                $sTitle = $this -> getParticipantsNames($iProfileId, $iLotId);
+
+            if ($aLotInfo[$CNF['FIELD_CLASS']] === BX_MSG_TALK_CLASS_MARKET) {
+                $aParticipants = $this->_oDb->getParticipantsList($iLotId, true, $iProfileId);
+                if (count($aParticipants) == 1) {
+                    $sOpponent = BxDolProfile::getInstance(current($aParticipants))->getDisplayName();
+                    $sTitle = $this->_oConfig->replaceConstant(_t('_bx_messenger_talk_types_market_title'), array('opponent' => $sOpponent));
+                }
+            }
+        }
+
+       $aVars = [
+                  'buttons' => $this->getTalkHeaderButtons($iLotId, $bIsBlockVersion),
+                  'title' => $sTitle,
+                  'menu_button' => !$bIsBlockVersion ? $this->parseHtmlByName('mobile-menu-button.html', []) : ''
+                ];
+
+        return $isArray || bx_is_api() ? $aVars : $this -> parseHtmlByName('talk-header.html', $aVars);
+    }
+
+    public function getHistoryTopArea($sContent = ''){
+	    return $this -> parseHtmlByName('history-top-area.html', ['content' => $sContent]);
+    }
+
+    public function getThreadTalkHeader($iProfileId, $iJotId){
+        $CNF = &$this->_oConfig->CNF;
+        $aParentLotInfo = $this->_oDb->getLotByJotId($iJotId);
+        if (empty($aParentLotInfo))
+            return '';
+
+        $iLotId = $aParentLotInfo[$CNF['FIELD_ID']];
+        $sTitle = $this -> getParticipantsNames($iProfileId, $aParentLotInfo[$CNF['FIELD_ID']]);
+        $aVars = array(
+            'buttons' => $this->getTalkHeaderButtons($iLotId, false),
+            'title' => $sTitle,
+            'menu_button' => $this->parseHtmlByName('mobile-menu-button.html', array())
+        );
+
+        return $this -> parseHtmlByName('talk-thread-header.html', $aVars);
+    }
+
+    public function getTalkHeaderButtons($iLotId, $bIsBlockVersion = false){
+        $CNF = &$this->_oConfig->CNF;
+	    $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_TALK_MENU']);
+	    $oMenu->setMessengerType($bIsBlockVersion);
+	    $oMenu->setTemplateById(BX_DB_MENU_TEMPLATE_TABS);
+        $oMenu->setContentId($iLotId);
+
+        return $oMenu->getCode();
+	}
+
+	/**
+	* Create top of the block with participants names and statuses
+	*@param int $iProfileId logged member id
+	*@param int $iLotId id of conversation. It can be empty if new talk
+    *@return string HTML code
+	*/
+	public function getParticipantsNames($iProfileId, $iLotId){
+		$aNickNames = array();
+
+		$aParticipantsList = $this->_oDb->getParticipantsList($iLotId, true, $iProfileId);
+		if (empty($aParticipantsList))
+			return '';
+		
+		$iCount = count($aParticipantsList);
+		$aParticipantsList = array_slice($aParticipantsList, 0, $this->_oConfig->CNF['PARAM_ICONS_NUMBER']);
+	
+		if (count($aParticipantsList) == 1)
+		{
+			$oProfile = $this -> getObjectUser($aParticipantsList[0]);
+			if ($oProfile)
+			{
+				$aNickNames['bx_repeat:users'][] = array(
+				    'profile_username' => $oProfile -> getUrl(),
+					'username' =>  $oProfile -> getDisplayName(),
+				);
+			}
+			$sCode = $this -> parseHtmlByName('status_usernames.html', $aNickNames);
+		}
+		else
+		{
+			foreach($aParticipantsList as $iParticipant)
+			{			
+				$oProfile = $this -> getObjectUser($iParticipant);
+				if ($oProfile)
+					$aNickNames[] = $oProfile->getDisplayName();
+			}
+			
+			$sOthers = $iCount > (int)$this -> _oConfig -> CNF['PARAM_ICONS_NUMBER'] ? _t('_bx_messenger_lot_title_participants_number', $iCount - (int)$this -> _oConfig -> CNF['PARAM_ICONS_NUMBER']) : '';
+			$sCode = $this -> parseHtmlByName('simple_usernames.html', array('usernames' => implode(', ', $aNickNames) . " {$sOthers}"));
+		}
+		
+		return $sCode;
+	}
+
+    function getProfileItem($iProfileId){
+        $oProfile = $this -> getObjectUser($iProfileId);
+
+        if (!$oProfile)
+            return false;
+
+        $sThumb = $oProfile->getThumb();
+        $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+        $sDisplayName = $oProfile->getDisplayName();
+
+        $iUserId = $oProfile -> id();
+        return [
+                'name' => $sDisplayName,
+                'id' => $iUserId,
+                'bx_if:avatars' => array(
+                    'condition' => $bThumb,
+                    'content' => array(
+                        'thumb' => $sThumb,
+                        'title' => $sDisplayName,
+                    )
+                ),
+                'bx_if:letters' => array(
+                    'condition' => !$bThumb,
+                    'content' => array(
+                        'color' => implode(', ', BxDolTemplate::getColorCode($iUserId, 1.0)),
+                        'title' => $sDisplayName,
+                        'letter' => mb_substr($sDisplayName, 0, 1)
+                    )
+                )
+            ];
+    }
+    function getProfilesListWithDesign($iLotId = 0, $aProfilesList = []){
+        $aProfilesList = empty($aProfilesList) && $iLotId ? $this->_oDb->getParticipantsList($iLotId) : $aProfilesList;
+        if (empty($aProfilesList))
+            return '';
+
+        $sProfilesList = '';
+        foreach($aProfilesList as &$iValue) {
+            if (!($aProfile = $this->getProfileItem($iValue)))
+                continue;
+
+            $sProfilesList .= $this->parseHtmlByName('edit-profiles-list-item.html', $aProfile);
+        }
+
+        return $sProfilesList;
+    }
+
+    public function _getBroadcastFields()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(!isset($CNF['OBJECT_FORM_FILTER']))
+            return [];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_FILTER'], $CNF['OBJECT_FORM_FILTER_DISPLAY'], $this);
+        if(!$oForm)
+            return [];
+
+        return $oForm->aInputs;
+    }
+
+    function getNotificationFormData(){
+        $aNotifFields = [];
+        if ($aNotifications = $this->_oDb->getNotificationsSettings($this->_oConfig->getName() . '_' . BX_MSG_NTFS_BROADCAST)) {
+            $aNotifValues = [];
+            foreach ($aNotifications as $sKey => $iValue) {
+                if ((int)$iValue)
+                    $aNotifValues[$sKey] = _t("_bx_messenger_broadcast_field_{$sKey}");
+            }
+
+            if (!empty($aNotifValues)){
+                $aNotifFields['notifications'] = [
+                    'type' => 'checkbox_set',
+                    'skip' => true,
+                    'caption' =>  _t('_bx_messenger_broadcast_field_notify'),
+                    'info' => _t('_bx_messenger_broadcast_field_notify_info'),
+                    'name' => 'notify_by',
+                    'attrs' => ['class' => 'flex items-center'],
+                    'attrs_wrapper' => ['class' => 'flex items-center'],
+                    'value' => array_keys($aNotifValues),
+                    'values' => $aNotifValues
+                ];
+            }
+        }
+
+        return $aNotifFields;
+    }
+
+    function getCreateListArea($iLotId = 0, $mixedProfiles = [], $bEmptyDefaultList = false){
+        $sContent = '';
+        $CNF = &$this->_oConfig->CNF;
+	    if ($this->_oConfig->CNF['SHOW-FRIENDS'] && !$bEmptyDefaultList)
+            $sContent = $this->getFriendsList();
+
+       $sProfilesList = $iLotId ? $this->getProfilesListWithDesign($iLotId, $mixedProfiles) : '';
+       $oCreateMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_CREATE_CONVO_MENU']);
+
+	   return $this->parseHtmlByName('create-list.html', [
+	       'items' => $sContent,
+           'menu_button' => $this->parseHtmlByName('mobile-menu-button.html', []),
+           'profiles_list' => $sProfilesList,
+           'bx_if:broadcast' => [
+                'condition' => $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_BROADCASTS) === true && !$iLotId,
+                'content' => [
+                    'convo_menu' => $oCreateMenu !== FALSE ? $oCreateMenu->getCode() : '',
+                ],
+           ],
+           'bx_if:edit' => [
+                'condition' => $iLotId,
+                'content' => [ 'id' => $iLotId ]
+              ]
+           ]);
+    }
+	/**
+	* Search friends function which shows fiends only if member have no any talks yet
+	*@param string $sParam keywords
+	*@return string html code
+	*/
+	function getFriendsList($sParam = '', $bListAsArray = false){
+		$iLimit = (int)$this->_oConfig->CNF['PARAM_FRIENDS_NUM_BY_DEFAULT'] ?? 5;
+
+		$sContent = MsgBox(_t('_Empty'));
+		if (!$this->_oConfig->CNF['SHOW-FRIENDS'])
+			return $sContent;
+
+         $aFriends = [];
+         if ($sParam)
+         {
+            $aUsers = BxDolService::call('system', 'profiles_search', array($sParam, $iLimit), 'TemplServiceProfiles');
+            if (empty($aUsers))
+                 return $sContent;
+
+            foreach($aUsers as &$aValue)
+                $aFriends[] = $aValue['value'];
+         }
+          else
+         {
+             bx_import('BxDolConnection');
+             $oConnection = BxDolConnection::getObjectInstance('sys_profiles_friends');
+             if (!$oConnection || !$aFriends = $oConnection->getConnectionsAsArray('content', bx_get_logged_profile_id(), 0, false, 0, $iLimit + 1, BX_CONNECTIONS_ORDER_ADDED_DESC))
+                 return $sContent;
+         }
+
+        return $this->getProfilesListPreviewForCreateTalkArea($aFriends, !$bListAsArray);
+	}
+
+	function getProfilesListPreviewForCreateTalkArea($aList, $bWrap = true){
+        $aItems = [];
+	    foreach($aList as &$iValue){
+            if (!($oProfile = $this -> getObjectUser($iValue)))
+                continue;
+
+            $sThumb = $oProfile->getThumb();
+            $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+            $sDisplayName = $oProfile->getDisplayName();
+
+            $aItems[] = array(
+                'name' => $sDisplayName,
+                'id' => $oProfile -> id(),
+                'bx_if:avatars' => array(
+                    'condition' => $bThumb,
+                    'content' => array(
+                        'thumb' => $sThumb,
+                        'title' => $sDisplayName,
+                    )
+                ),
+                'bx_if:letters' => array(
+                    'condition' => !$bThumb,
+                    'content' => array(
+                        'color' => implode(', ', BxDolTemplate::getColorCode($iValue, 1.0)),
+                        'title' => $sDisplayName,
+                        'letter' => mb_substr($sDisplayName, 0, 1)
+                    )
+                )
+            );
+        }
+
+        if (!$bWrap)
+            return $aItems;
+
+        return $this -> parseHtmlByName('profiles-list.html', ['bx_repeat:profiles' => $aItems]);
+    }
+	/**
+	*  List of Lots (left side block content)
+	*@param int $iProfileId logged member id
+	*@param array $aLots list of lost to show
+	*@param boolean $bShowTime display time(last message) in the right side of the lot
+	*@return string html code
+	*/
+	function getLotsPreview($iProfileId, $aLots, $bShowTime = true){
+		$CNF = &$this->_oConfig->CNF;
+
+		$sContent = '';
+		$aContent = [];
+		foreach($aLots as &$aLot) {
+          $aVars = [];
+		  $aParticipantsList = $this -> _oDb -> getParticipantsList($aLot[$CNF['FIELD_ID']], true, $iProfileId);
+		  $iParticipantsCount = count($aParticipantsList);
+		  $aParticipantsList = $iParticipantsCount ? array_slice($aParticipantsList, 0, $CNF['PARAM_ICONS_NUMBER']) : [$iProfileId];
+          $oAuthor = null;
+
+          $sGroupName = $sModuleTitle = $sThumb = $sModuleIcon = '';
+          $bIsGroupTalk = isset($aLot[$CNF['FMGL_GROUP_ID']]) && (int)$aLot[$CNF['FMGL_GROUP_ID']];
+          if ($bIsGroupTalk) {
+                $aGroupInfo = $this->_oDb->getGroup($aLot[$CNF['FMGL_GROUP_ID']]);
+                if ($aGroupInfo[$CNF['FMG_MODULE']] !== BX_MSG_TALK_TYPE_PAGES) {
+                    $oGroupProfile = BxDolProfile::getInstance($aGroupInfo[$CNF['FMG_PROFILE_ID']]);
+                    if ($oGroupProfile) {
+                        $sThumb = $oGroupProfile->getIcon();
+                        if (!$sThumb)
+                            $sThumb = bx_srv($aGroupInfo[$CNF['FMG_MODULE']], 'get_thumb', [$oGroupProfile->getContentId()]);
+
+                        $aVars['author_module'] = $sModule = $oGroupProfile->getModule();
+                        $sModuleTitle = _t('_' . $oGroupProfile->getModule());
+                        $aGroupItemInfo = BxDolService::call($oGroupProfile->getModule(), 'get_info', [$oGroupProfile->getContentId(), false]);
+                        if (!empty($aGroupItemInfo)) {
+                            $oModule = BxDolModule::getInstance($aGroupInfo[$CNF['FMG_MODULE']]);
+                            if ($oModule->_oConfig) {
+                                $oMCNF = $oModule->_oConfig->CNF;
+                                $sGroupName = isset($aGroupItemInfo[$oMCNF['FIELD_TITLE']]) ? $aGroupItemInfo[$oMCNF['FIELD_TITLE']] : '';
+                            }
+                        }
+                    }
+
+                    if (!$sThumb)
+                        $sThumb = BxDolModule::getInstance($aGroupInfo[$CNF['FMG_MODULE']])->_oTemplate->getImageUrl('no-picture.svg');
+
+                    $sModuleIcon = $sThumb;
+                } else
+                {
+                    $sModuleIcon = $this->_oConfig->getPageIcon();                    
+                    $sThumb = $sModuleIcon;
+
+                    $aVars['author_module'] = 'bx_pages';
+                    $sModuleTitle = _t('_bx_messenger_pages');
+                    $aUrl = array();
+                    parse_str($aGroupInfo[$CNF['FMG_URL']], $aUrl);
+                    if (isset($aUrl['i']) && ($oPage = BxTemplPage::getObjectInstanceByURI($aUrl['i']))) {
+                        $aPageObject = $oPage->getObject();
+                        $sGroupName = _t($aPageObject['title']);
+                    }
+                }
+
+                $aVars['bx_repeat:avatars'][] = [
+                    'bx_if:avatars' => [
+                            'condition' => true,
+                            'content' => [
+                                'title' => $sModuleTitle,
+                                'thumb' => $sModuleIcon,
+                        ]
+                    ],
+                    'bx_if:letters' => [
+                        'condition' => false,
+                        'content' => []
+                    ]
+                ];
+            }
+            else
+            {
+                $oAuthor = $this->getObjectUser(
+                    $aLot[$CNF['FIELD_TYPE']] == BX_IM_TYPE_BROADCAST || $iParticipantsCount !== 1
+                        ? $aLot[$CNF['FIELD_AUTHOR']]
+                        : current($aParticipantsList)
+                );
+
+                $sThumb = $oAuthor->getThumb();
+                $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+                $sTitle = $oAuthor->getDisplayName();
+                    
+                $aVars['bx_repeat:avatars'][] = array(
+                    'bx_if:avatars' => array(
+                        'condition' => $bThumb,
+                        'content' => array(
+                            'title' => $sTitle,
+                            'thumb' => $sThumb,
+                        )
+                    ),
+                    'bx_if:letters' => array(
+                        'condition' => !$bThumb,
+                        'content' => array(
+                            'color' => implode(', ', BxDolTemplate::getColorCode($aLot[$CNF['FIELD_AUTHOR']], 1.0)),
+                            'letter' => mb_substr($sTitle, 0, 1)
+                        )
+                    )
+                );
+            }
+
+            if (!$CNF['HIDE-PARTS']) {
+                $aLastAnsweredParticipants = $this->_oDb->getLatestJotsAuthors($aLot[$CNF['FIELD_ID']]);
+                $aParticipantsViewList = [];
+                foreach ($aLastAnsweredParticipants as $iParticipant) {
+                    $oProfile = $this->getObjectUser($iParticipant);
+                    if ($oProfile) {
+                        $sThumb = $oProfile->getThumb();
+                        $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+                        $sDisplayName = $oProfile->getDisplayName();
+                        $aParticipantsViewList[] = array(
+                            'bx_if:avatars' => array(
+                                'condition' => $bThumb,
+                                'content' => array(
+                                    'title' => $sDisplayName,
+                                    'thumb' => $sThumb,
+                                )
+                            ),
+                            'bx_if:letters' => array(
+                                'condition' => !$bThumb,
+                                'content' => array(
+                                    'color' => implode(', ', BxDolTemplate::getColorCode($iParticipant, 1.0)),
+                                    'letter' => mb_substr($sDisplayName, 0, 1)
+                                )
+                            )
+                        );
+                    }
+                }
+
+                $aVars['bx_if:parts_list'] = ['condition' => true, 'content' => ['bx_repeat:participants' => $aParticipantsViewList]];
+            } else
+                $aVars['bx_if:parts_list'] = ['condition' => false, 'content' => []];
+			
+			if (!empty($aLot[$CNF['FIELD_TITLE']]))
+				$sTitle = _t($aLot[$CNF['FIELD_TITLE']]);
+			else
+			{
+                $aNickNamesTitles = [];
+                foreach ($aParticipantsList as &$iPartId)
+                    $aNickNamesTitles[] = $this->getObjectUser($iPartId)->getDisplayName();
+
+			    if ($iParticipantsCount > 3)
+					$sTitle = implode(', ', array_slice($aNickNamesTitles, 0, $CNF['PARAM_ICONS_NUMBER'])) . '...';
+				else
+					$sTitle = implode(', ', $aNickNamesTitles);
+			}
+
+			$sStatus = $sCount = '';
+			if ($iParticipantsCount <= 1 && $oAuthor && !$bIsGroupTalk){
+                $sStatus = (method_exists($oAuthor, 'isOnline') ? $oAuthor -> isOnline() : false) ?
+					$this -> getOnlineStatus($oAuthor-> id(), 1) :
+					$this -> getOnlineStatus($oAuthor-> id(), 0) ;
+			}
+			else
+                $sCount = '<div class="bx-def-label bx-def-font-middle status">' . $iParticipantsCount .'</div>';
+
+			$aVars[$CNF['FIELD_ID']] = $aLot[$CNF['FIELD_ID']];
+			$aVars[$CNF['FIELD_TITLE']] = $sTitle;
+			$aVars['number'] = $sCount;
+			$aVars['status'] = $sStatus;
+
+			$aLatestJots = $this -> _oDb -> getLatestJot($aLot[$CNF['FIELD_ID']]);
+			$aVars[$CNF['FIELD_MESSAGE']] = $aVars['sender_username'] = '';
+
+            $iTime = bx_time_js($aLot[$CNF['FIELD_ADDED']], BX_FORMAT_DATE);
+            if (bx_is_api()) {
+                $iTime = $aLot[$CNF['FIELD_ADDED']];
+            }
+
+            $aVars[$CNF['FIELD_AUTHOR']] = $aLot[$CNF['FIELD_AUTHOR']];
+
+            $sMessage = '';
+			if (!empty($aLatestJots)) {
+                $aAttachType = [];
+                if ($aLatestJots[$CNF['FIELD_MESSAGE_AT_TYPE']])
+                    $aAttachType = array($aLatestJots[$CNF['FIELD_MESSAGE_AT_TYPE']] => $aLatestJots[$CNF['FIELD_MESSAGE_AT']]);
+                else if ($aLatestJots[$CNF['FIELD_MESSAGE_AT']])
+                    $aAttachType = @unserialize($aLatestJots[$CNF['FIELD_MESSAGE_AT']]);
+
+				if (isset($aLatestJots[$CNF['FIELD_MESSAGE']])) {
+                    $sMessage = preg_replace( '/<br\W*?\/>|\n/', " ", $aLatestJots[$CNF['FIELD_MESSAGE']]);
+				    $sMessage = html2txt($sMessage);
+                    if (isset($aAttachType[BX_ATT_TYPE_REPOST]))
+                    {
+                        $sMessage = $this -> _oConfig -> cleanRepostLinks($sMessage, $aAttachType[BX_ATT_TYPE_REPOST]);
+                        $sMessage = $sMessage ? $sMessage : _t('_bx_messenger_repost_message');
+                    }
+
+                    $sMessage = BxTemplFunctions::getInstance()->getStringWithLimitedLength($sMessage, $CNF['MAX_PREV_JOTS_SYMBOLS']);
+				}
+
+                if (!$sMessage){
+                    if (isset($aAttachType[BX_ATT_TYPE_FILES]))
+                        $sMessage = _t('_bx_messenger_attached_files_message', $this -> _oDb -> getJotFiles($aLatestJots[$CNF['FIELD_MESSAGE_ID']], true));
+
+                    if (isset($aAttachType[BX_ATT_TYPE_GIPHY]))
+                        $sMessage = _t('_bx_messenger_attached_giphy_message');
+
+                    if ((int)$aLatestJots[$CNF['FIELD_MESSAGE_VIDEOC']])
+                        $sMessage = _t('_bx_messenger_lots_menu_video_conf_start');
+                }
+
+				if ($oSender = $this -> getObjectUser($aLatestJots[$CNF['FIELD_MESSAGE_AUTHOR']]))
+				{
+					$aVars['sender_username'] = $oSender -> id() == $iProfileId ? _t('_bx_messenger_you_username_title') : $oSender -> getDisplayName();
+                    $sModuleIcon = $sModuleIcon ? $sModuleIcon : $oSender -> getIcon();
+				}
+				
+				$iTime = bx_is_api() ? $aLatestJots[$CNF['FIELD_MESSAGE_ADDED']] : bx_time_js($aLatestJots[$CNF['FIELD_MESSAGE_ADDED']], BX_FORMAT_DATE);
+			}
+
+			$iUnreadJotsCount = $this->_oDb->getNewJots($iProfileId, $aLot[$CNF['FIELD_ID']], true);
+
+            $aVars['class'] = $iUnreadJotsCount ? 'unread-lot' : '';
+			$aVars['title_class'] = $iUnreadJotsCount ? 'bx-def-font-extrabold' : '';
+			$aVars['message_class'] = $iUnreadJotsCount ? 'bx-def-font-semibold' : '';
+			$aVars['view_in_chat'] = '';
+			$aVars['bubble_class'] = $iUnreadJotsCount ? '' : 'hidden';
+			$aVars['count'] = $iUnreadJotsCount;
+                        $aVars['participants'] = $aLot[$CNF['FIELD_PARTICIPANTS']];
+                        
+
+            $aVars['bx_if:user'] = [
+                'condition' => $sModuleIcon,
+                'content' => [
+                    'talk_type' => $bIsGroupTalk ? $sModuleTitle : $aVars['sender_username'],
+                    'icon' => $sModuleIcon,
+                    'message' => $bIsGroupTalk && $sGroupName ? $sGroupName : $sMessage
+                ]
+            ];
+
+	        $aVars['bx_if:timer'] = ['condition' => $bShowTime, 'content' => [ 'time' => $iTime ]];
+	        $oTemplate = &$this;
+            bx_alert($this->_oConfig->getObject('alert'), 'talk_preview_data', $aLot[$CNF['FIELD_ID']], $aLot[$CNF['FIELD_ID']], [
+               'vars' => &$aVars,
+               'template' => &$oTemplate,
+               'talk' => $aLot,
+            ]);
+
+			$aContent[] = $aVars;
+			$sContent .= $oTemplate->parseHtmlByName('lots-briefs.html',  $aVars);
+		}
+		
+		return  bx_is_api() ? $aContent : $sContent;
+	}
+  
+  	/**
+	* Builds top talk area with Profiles names and Statuses
+	*@param int $iProfileId logget member id
+	*@param int $iStatus member status
+	*@return string html code
+	*/
+	private function getOnlineStatus($iProfileId, $iStatus){
+	    switch($iStatus){
+			case 0:
+					$sTitle = _t('_bx_messenger_offline');
+					$sClass = 'offline';
+				break;
+			case 2:
+					$sTitle = _t('_bx_messenger_away');
+					$sClass = 'away';
+				break;
+			default:
+					$sTitle = _t('_bx_messenger_online');
+					$sClass = 'online';
+		}
+
+
+		return !bx_is_api() ? $this -> parseHtmlByName('online-status.html', array(
+			'id' => (int)$iProfileId,
+			'title' => $sTitle,
+			'class' => $sClass
+		)) : $sClass ;
+	}
+
+    /**
+     * Returns html with users show viewed the message
+     * @param $iJotId int message id
+     * @param $iExcludeProfile int exclude defined profile id from the list
+     * @return bool|string
+     */
+	public function getViewedJotProfiles($iJotId, $iExcludeProfile = 0){
+        $CNF = $this->_oConfig->CNF;
+        $aJotInfo = $this -> _oDb -> getJotById($iJotId);
+        if (empty($aJotInfo))
+            return '';
+
+        $aResult = $aParticipants = $this -> _oDb -> getParticipantsList($aJotInfo[$CNF['FIELD_MESSAGE_FK']], true);
+        if ($CNF['MAX_VIEWS_PARTS_NUMBER'] < count($aResult))
+            return '';
+
+        $aUnreadProfiles = $this->_oDb->getForWhomJotIsNew($aJotInfo[$CNF['FIELD_MESSAGE_FK']], $iJotId);
+        if (!empty($aUnreadProfiles))
+            $aResult = array_diff($aParticipants, $aUnreadProfiles);
+        else
+            $aResult = $aParticipants;
+
+        $aIcons = array();
+        foreach($aResult as &$iProfileId) {
+            if ($iExcludeProfile && +$iExcludeProfile == +$iProfileId)
+                continue;
+
+            if ($oProfile = BxDolProfile::getInstance($iProfileId))
+                $aIcons[] = array(
+                    'id' => $iProfileId,
+                    'icon' => $oProfile->getIcon(),
+                    'name' => $oProfile->getDisplayName(),
+                );
+        }
+
+        return $this -> parseHtmlByName('viewed.html', array(
+            'bx_repeat:viewed' => $aIcons
+        ));
+    }
+	/**
+	* Get jots list by specified criteria
+	*@param int $iProfileId logged member id
+	*@param array $aParams options
+	*	- int $iLotId 
+	*	- string $sUrl of the lot block
+	*	- int $iStart jot's id from which to load the messages
+	*	- string $sLoad type of the load (new jots or prev from history) 
+	*	- int $iLimit number of jots
+	*	- boolean $bDisplay make jots visible before loading
+	*	- string html code
+	*	- boolean load history from defined jot id and to select it
+    *@return array HTML code
+	*/
+	public function getJotsOfLot($iProfileId, $aParams){
+        $CNF = &$this -> _oConfig -> CNF;
+		$iLotId = isset($aParams['lot_id']) ? (int)$aParams['lot_id'] : BX_IM_EMPTY;
+		$sUrl = isset($aParams['url']) ? $aParams['url'] : BX_IM_EMPTY_URL;
+		$iStart = isset($aParams['start']) ? (int)$aParams['start'] : BX_IM_EMPTY; 
+		$sLoad = isset($aParams['load']) ? $aParams['load'] : 'new';
+		$iLimit = isset($aParams['limit']) ? (int)$aParams['limit'] : BX_IM_EMPTY; 
+		$bDisplay = isset($aParams['display']) ? (bool)$aParams['display'] : false;
+		$bSelectJot = isset($aParams['select']) ? (bool)$aParams['select'] : false;
+		$bMarkAsRead = isset($aParams['read']) && $aParams['read'] === true;
+        $bShowViews = isset($aParams['views']);
+        $bDynamic = isset($aParams['dynamic']);
+        $iParent = isset($aParams['parent']) && $aParams['parent'] ? $aParams['parent'] : 0;
+        $sArea = isset($aParams['area']) ? $aParams['area'] : 0;
+
+        $aResult = array('content' => '');
+		$aLotInfo = $this -> _oDb -> getLotByIdOrUrl($iLotId, $sUrl, $iProfileId);
+		if (empty($aLotInfo))
+			return bx_is_api() ? [] : $aResult;
+
+		if ($bSelectJot && $iStart){
+		    $aStartMiddleJot = $this -> _oDb -> getJotsByLotId([
+		        'lot' => $aLotInfo[$CNF['FIELD_ID']],
+                'start' => $iStart,
+                'mode' => 'prev',
+                'area' => $sArea,
+                'limit' => (int)$CNF['MAX_JOTS_BY_DEFAULT']/2]);
+
+			if (!empty($aStartMiddleJot))
+			    $iStart = current($aStartMiddleJot)[$CNF['FIELD_MESSAGE_ID']];
+		}
+		
+		$aJots = $this->_oDb->getJotsByLotId(['lot' => $aLotInfo[$CNF['FIELD_ID']],
+                                              'start' => $iStart,
+                                              'mode' => $sLoad,
+                                              'area' => $sArea,
+                                              'limit' => $iLimit,
+                                              'include' => $bSelectJot && $iStart,
+                                              'parent' => $iParent]);
+		if (bx_is_api())
+            return $aJots;
+                
+		if (empty($aJots))
+			return $aResult;
+
+        $iJotCount = count($aJots);
+		$aVars['bx_repeat:jots'] = array();
+        $iFirstUnreadJot = $this->_oDb->getFirstUnreadJot($iProfileId, $iLotId);
+        $iTimeFromToShift = $iPrevAuthor = 0;
+		foreach($aJots as $iKey => $aJot) {
+            $oProfile = $this->getObjectUser($aJot[$CNF['FIELD_MESSAGE_AUTHOR']]);
+            $iJot = $aJot[$CNF['FIELD_MESSAGE_ID']];
+            $bShowDateSeparator = false;
+            if (!$iTimeFromToShift || (($iTimeFromToShift + $CNF['DATE-SHIFT']) < $aJot[$CNF['FIELD_MESSAGE_ADDED']])){
+                $bShowDateSeparator = true;
+                $iTimeFromToShift = $aJot[$CNF['FIELD_MESSAGE_ADDED']];
+            }
+
+            if ($oProfile) {
+                    $sReply = $sAttachment = $sMessage = '';
+                    $bIsTrash = (int)$aJot[$CNF['FIELD_MESSAGE_TRASH']];
+                    $iIsVC = (int)$aJot[$CNF['FIELD_MESSAGE_VIDEOC']];
+                    $bIsLotAuthor = $this->_oDb->isAuthor($iLotId, $iProfileId);
+                    $isAllowedDelete = $this->_oDb->isAllowedToDeleteJot($aJot[$CNF['FIELD_MESSAGE_ID']], $iProfileId, $aJot[$CNF['FIELD_MESSAGE_AUTHOR']], $aJot[$CNF['FIELD_MESSAGE_FK']]);
+
+                    if ($bIsTrash || ($iIsVC && !$aJot[$CNF['FIELD_MESSAGE']]))
+                        $sMessage = $this->getMessageIcons($aJot[$CNF['FIELD_MESSAGE_ID']], $bIsTrash ? 'delete' : 'vc', isAdmin() || $bIsLotAuthor);
+                    else
+                    {
+                        if ($aLotInfo[$CNF['FIELD_TYPE']] != BX_IM_TYPE_BROADCAST || ($aLotInfo[$CNF['FIELD_TYPE']] == BX_IM_TYPE_BROADCAST && empty($aJot[$CNF['FIELD_MESSAGE_AT']])))
+                        $sMessage = $this->_oConfig->bx_linkify($aJot[$CNF['FIELD_MESSAGE']]);
+
+                        if (!empty($aJot[$CNF['FIELD_MESSAGE_AT']])) {
+                            $aAttachments = $this->getAttachment($aJot);
+                            if (!empty($aAttachments[BX_ATT_GROUPS_ATTACH])) {
+                                $sAttachment = $aAttachments[BX_ATT_GROUPS_ATTACH];
+
+                                $bIsEmpty = false;
+                                bx_alert($this->_oConfig->getObject('alert'), 'attachment_before', $iJot, $iLotId, [
+                                    'is_empty' => &$bIsEmpty,
+                                    'attachment' => &$sAttachment
+                                ]);
+
+                                if ($bIsEmpty) {
+                                    $aVars['bx_repeat:jots'][] = $this->getEmptyMessageTemplate(['attachment' => $sAttachment, 'id' => $iJot]);
+
+                                    if ($bMarkAsRead)
+                                        $this->_oDb->readMessage($iJot, $iProfileId);
+
+                                    continue;
+                                }
+                            }
+                        }
+
+                        if (($iReplyId = (int)$aJot[$CNF['FIELD_MESSAGE_REPLY']]) && ($aReplyJot = $this->_oDb->getJotById($iReplyId))) {
+                            $sReply = $this->parseHtmlByName('reply.html', [
+                                'id' => $iReplyId,
+                                'message' => $this->getReplyPreview($iReplyId),
+                            ]);
+                        }
+                    }
+
+                $sDisplayName = $oProfile->getDisplayName();
+                $sThumb = $oProfile->getThumb();
+                $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+
+                $sReactions = $this->getJotReactions($iJot);
+				$aVars['bx_repeat:jots'][] = [
+                    'new' => (int)($iFirstUnreadJot && $iJot >= $iFirstUnreadJot),
+                    'immediately' => +$this->_oConfig->CNF['REMOVE_MESSAGE_IMMEDIATELY'],
+                    'bx_if:show_author' => array(
+                        'condition' => $iPrevAuthor !== $oProfile->id(),
+                        'content' => array(
+                            'url' => $oProfile->getUrl(),
+                            'bx_if:avatars' => array(
+                                'condition' => $bThumb,
+                                'content' => array(
+                                    'thumb' => $sThumb,
+                                    'title' => $sDisplayName,
+                                )
+                            ),
+                            'bx_if:letters' => array(
+                                'condition' => !$bThumb,
+                                'content' => array(
+                                    'color' => implode(', ', BxDolTemplate::getColorCode($aJot[$CNF['FIELD_MESSAGE_AUTHOR']], 1.0)),
+                                    'letter' => mb_substr($sDisplayName, 0, 1)
+                                )
+                            ),
+                        )
+                    ),
+                    'bx_if:show_title' => array(
+                        'condition' => $iPrevAuthor !== $oProfile->id(),
+                        'content' => array(
+                            'title' => $sDisplayName,
+                        )
+                    ),
+                    'bx_if:time-separator' => array(
+                        'condition' => $bShowDateSeparator,
+                        'content' => [
+                            'date' => $this-> getDateSeparator($aJot[$CNF['FIELD_MESSAGE_ADDED']]),
+                        ]
+                    ),
+                    'bx_if:new' => array(
+                        'condition' => $iPrevAuthor !== $oProfile->id(),
+                        'content' => array()
+                    ),
+                    'id' => $aJot[$CNF['FIELD_MESSAGE_ID']],
+                    'message' => preg_replace('/(?:\s*<br *\/?>\s*)+$/', "", $sMessage),
+                    'attachment' => $sAttachment,
+					'reply' => $sReply,
+                    'my' => (int)$iProfileId === (int)$aJot[$CNF['FIELD_MESSAGE_AUTHOR']] ? 1 : 0,
+                    'bx_if:jot_menu' => array(
+                        'condition' => $iProfileId && !$bIsTrash,
+                        'content' => array(
+                            'jot_menu' => $this->getJotMenuCode($iJot)
+                        )
+                    ),
+                    'icons' => $sMessage ? $this->parseHtmlByName('jot-icons.html', array(
+                        'edit_icon' => $aJot[$CNF['FIELD_MESSAGE_EDIT_BY']] && !$bIsTrash ?
+                            $this->parseHtmlByName('edit-icon.html',
+                                array(
+                                    'edit' => _t('_bx_messenger_edit_by',
+                                        bx_process_output($aJot[$CNF['FIELD_MESSAGE_LAST_EDIT']], BX_DATA_DATETIME_TS),
+                                        $this->getObjectUser($aJot[$CNF['FIELD_MESSAGE_EDIT_BY']])->getDisplayName()),
+                                )
+                            ) : '',
+                        'views' => $bShowViews && ($iJotCount - 1 == $iKey) ? $this->getViewedJotProfiles($iJot, $iProfileId) : '',
+                    )) : '',
+                    'bx_if:show_reactions_area' => array(
+                        'condition' => !$bIsTrash,
+                        'content' => array(
+                            'bx_if:reactions' => array(
+                                'condition' => true,
+                                'content' => array(
+                                    'reactions' => $sReactions,
+                                    'bx_if:reactions_menu' => array(
+                                        'condition' => $iProfileId,
+                                        'content' => array(
+                                            'display' => $sReactions ? 'block' : 'none',
+                                        )
+                                    ),
+                                )
+                            ),
+                            'bx_if:edit' => array(
+                                'condition' => $isAllowedDelete,
+                                'content' => array()
+                            ),
+                        )
+                    ),
+                    'thread_replies' => $this->getThreadReply($iJot),
+                    'display' => !$bDisplay ? 'style="display:none;"' : '',
+                    'bx_if:blink-jot' => array(
+                        'condition' => $bSelectJot && $aParams['start'] == $iJot,
+                        'content' => array()
+                    ),
+                    'display_message' => '',
+                    'view_in_chat' => in_array($sArea, $CNF['VIEW-IN-TALKS']) ?
+                                        $this->parseHtmlByName('view-in-chat.html', array('lot' => $iLotId, 'jot' => $iJot)) : "",
+                    'message_class' => !$sMessage ? 'hidden' : '',
+                ];
+
+                if ($bMarkAsRead)
+                    $this->_oDb->readMessage($aJot[$CNF['FIELD_MESSAGE_ID']], $iProfileId);
+
+                $iPrevAuthor = $oProfile->id();
+            }
+        }
+
+        if ($bMarkAsRead)
+            $this->_oDb->markNotificationAsRead($iProfileId, $iLotId);
+
+		return ['content' => $this -> parseHtmlByName('jots.html',  $aVars),
+                'first_jot' => $sLoad == 'prev' ? $aJots[count($aJots) - 1] : $aJots[0]];
+	}
+
+	function getDateSeparator($iDate){
+	    return $this->parseHtmlByName('date-separator.html', array('date' => $this->_oConfig->getSeparatorTime($iDate)));
+    }
+
+    /**
+     * Builds left column with content
+     *@param int $iProfileId logged member id
+     *@return string html code
+     */
+    public function getLotsList($iProfileId, $iSelectedLotId = 0){
+        $CNF = &$this->_oConfig->CNF;
+        $aMyLots = $this->_oDb->getMyLots($iProfileId);
+        $sContent = MsgBox(_t('_Empty'));
+        if (!empty($aMyLots)) {
+            if ($iSelectedLotId && array_search($iSelectedLotId, array_column($aMyLots, $CNF['FIELD_ID'])) === false) {
+                $aPrependConvo = $this->_oDb->getLotInfoById($iSelectedLotId);
+                $aMyLots = array_merge([$aPrependConvo], $aMyLots);
+            }
+
+            $sContent = $this->getLotsPreview($iProfileId, $aMyLots);
+        }
+
+        $bSimpleMode = $this->_oConfig->CNF['SHOW-SEARCH-BOX'];
+
+        $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_TALK_MENU']);
+		$aVars = [
+                    'items' => $sContent,
+                    'custom_class_title' => $bSimpleMode ? 'hidden' : 'block',
+                    'custom_class_search' => $bSimpleMode ? 'block' : 'hidden',
+                    'search_for_title' => bx_js_string(_t('_bx_messenger_search_for_lost_title')),
+                    'bx_repeat:menu' => [['menu_title' => _t("_bx_messenger_lots_type_all"), 'type' => 0, 'count' => '']],
+                    'star_icon' => $CNF['STAR_ICON'],
+                    'star_color' => $CNF['STAR_BACKGROUND_COLOR'],
+                    'bx_if:create' => array(
+                        'condition' => $this->_oConfig->isAllowedAction(BX_MSG_ACTION_CREATE_TALKS, $iProfileId) === true,
+                        'content' => array(
+                            'create_lot_title' => bx_js_string(_t('_bx_messenger_lots_menu_create_lot_title')),
+                        )
+                    ),
+                    'bx_if:featured' => array(
+                        'condition' => $oMenu->isActive('star'),
+                        'content' => array(
+                            'star_title' => bx_js_string(_t('_bx_messenger_lots_menu_star_title')),
+                        )
+                    )
+                ];
+
+		return  bx_is_api() ? $aVars : $this -> parseHtmlByName('lots-list.html', $aVars);
+	}
+
+    /**
+     * Create js configuration for the messenger depends on administration settings
+     * @param int $iProfileId logged member id
+     * @param bool $bBlockVersion
+     * @param int $iLotId
+     * @param int $iJotId
+     * @param int $iPersonToTalk
+     * @param int $iType
+     * @return string html code
+     */
+	public function loadConfig($iProfileId, $aParams){
+	    $CNF = &$this->_oConfig->CNF;
+	    $aUrlInfo = parse_url(BX_DOL_URL_ROOT);
+
+        $bBlockVersion = isset($aParams['is_block_version']) ? (bool)$aParams['is_block_version'] : true;
+        $iLotId = isset($aParams['lot']) ? (int)$aParams['lot'] :  BX_IM_EMPTY;
+        $iJotId = isset($aParams['jot']) ? (int)$aParams['jot'] : BX_IM_EMPTY;
+        $iPersonToTalk = isset($aParams['selected_profile']) ? (int)$aParams['selected_profile'] : BX_IM_EMPTY;
+        $iType =  isset($aParams['type']) ? (int)$aParams['type'] : BX_IM_TYPE_PRIVATE;
+        $sWelcomeMessage = isset($aParams['welcome']) ? strval($aParams['welcome']) : '';
+
+	    $oEmbed = BxDolEmbed::getObjectInstance();
+        $sEmbedTemplate = '';
+		if($oEmbed && $CNF['USE_EMBEDLY'])
+           $sEmbedTemplate = $oEmbed->getLinkHTML('__url__');
+
+        $this->addJsTranslation(array(
+            '_bx_messenger_online',
+            '_bx_messenger_offline',
+            '_bx_messenger_away',
+            '_bx_messenger_repost_message',
+            '_bx_messenger_close_video_confirm',
+            '_bx_messenger_video_recorder_is_not_available',
+            '_bx_messenger_video_recorder_is_blocked',
+            '_bx_messenger_max_video_file_exceeds',
+            '_bx_messenger_video_record_is_not_supported',
+            '_bx_messenger_search_no_results',
+            '_bx_messenger_search_query_issue',
+            '_bx_messenger_wait_for_uploading',
+            '_bx_messenger_are_you_sure_close_jisti',
+            '_bx_messenger_jisti_connection_error',
+            '_bx_messenger_post_area_message',
+            '_bx_messenger_jitsi_mobile_warning',
+            '_bx_messenger_loading',
+            '_bx_messenger_share_jot',
+            '_bx_messenger_notification_request',
+            '_bx_messenger_notification_request_yes',
+            '_bx_messenger_notification_request_no',
+            '_bx_messenger_remove_jot_confirm',
+            '_bx_messenger_post_confirm_delete_file',
+            '_bx_messenger_delete_lot',
+            '_bx_messenger_clear_lot',
+            '_bx_messenger_are_you_sure_leave',
+            '_bx_messenger_lot_parts_empty',
+            '_bx_messenger_jot_menu_remove_save',
+            '_bx_messenger_jot_menu_save',
+            '_bx_messenger_reply_audio_file'
+        ));
+
+        $sUsername = '';
+        $oProfile = $this -> getObjectUser($iProfileId);
+        if($oProfile)
+            $sUsername = bx_js_string($oProfile -> getDisplayName());
+
+        $sUrl = $this->_oConfig->getPageIdent();
+        if ($iPersonToTalk && ($oModuleProfile = BxDolProfile::getInstance($iPersonToTalk))) {
+            $sModule = $oModuleProfile->getModule();
+            $bIsProfile = BxDolRequest::serviceExists($sModule, 'act_as_profile') && BxDolService::call($sModule, 'act_as_profile');
+            if (BxDolRequest::serviceExists($sModule, 'is_group_profile') && BxDolService::call($sModule, 'is_group_profile') && !$bIsProfile) {
+                $aOwnerInfo = BxDolService::call($sModule, 'get_info', array($oModuleProfile->getContentId(), false));
+                if (!empty($aOwnerInfo) && is_array($aOwnerInfo) && BxDolService::call($sModule, 'check_allowed_view_for_profile', array($aOwnerInfo)) === CHECK_ACTION_RESULT_ALLOWED) {
+                    $oModule = BxDolModule::getInstance($sModule);
+                    if ($oModule->_oConfig) {
+                        $oMCNF = $oModule->_oConfig->CNF;
+
+                        $sUrl = "i={$oMCNF['URI_VIEW_ENTRY']}&id=" . $oModuleProfile->getContentId();
+                        if ($sUrl && $aTalk = $this->_oDb->getLotByUrl($sUrl))
+                            $iLotId = $aTalk[$CNF['FIELD_ID']];
+                        else
+                        {
+                            $sTalkTitle = $oModuleProfile->getDisplayName();
+                            $iType = $this->_oConfig->getTalkType($sModule);
+                            $iLotId = $this->_oDb->createLot($iProfileId, array('url' => $sUrl, 'title' => $sTalkTitle, 'type' => $iType, 'participants' => array($iProfileId)));
+                        }
+                    }
+                }
+            }
+        }
+
+        $iGroupId = 0;
+        if ($iLotId && ($aLotInfo = $this->_oDb->getLotInfoById($iLotId))){
+            if ($aLotInfo[$CNF['FIELD_TYPE']] != BX_IM_TYPE_PRIVATE && isset($aLotInfo[$CNF['FIELD_URL']]))
+                $sUrl = $aLotInfo[$CNF['FIELD_URL']];
+
+            $iType = $aLotInfo[$CNF['FIELD_TYPE']];
+            $iGroupId = (int)$aLotInfo[$CNF['FMGL_GROUP_ID']] ? (int)$aLotInfo[$CNF['FMGL_GROUP_ID']] : 0;
+        };
+
+        $aUnreadJotsInfo = $this->_oDb->getNewJots($iProfileId, $iLotId);
+
+        $iUnreadJotsNumber = $iLastUnreadJot = 0;
+        $iStartJot = (int)$iJotId;
+        if (!empty($aUnreadJotsInfo)){
+            $iStartJot = $iStartJot ? $iStartJot : (int)$aUnreadJotsInfo[$CNF['FIELD_NEW_JOT']];
+            $iUnreadJotsNumber = (int)$aUnreadJotsInfo[$CNF['FIELD_NEW_UNREAD']];
+            $iLastUnreadJot = (int)$aUnreadJotsInfo[$CNF['FIELD_NEW_JOT']];
+        }
+
+        $bAttach = true;
+        if ($iStartJot)
+            $bAttach = $this->_oDb->getJotsNumber($iLotId, $iStartJot) < (int)$CNF['MAX_JOTS_BY_DEFAULT']/2;
+
+        $sJotJWT = $CNF['JOT-JWT'];
+        $aUnreadJotsStat = $this->_oDb->getUnreadMessagesStat($iProfileId);
+        $aVars = [
+			'profile_id' => (int)$iProfileId,
+            'group_id' => $iGroupId,
+            'username' => $sUsername,
+            'lot' => (int)$iLotId,
+            'url' => $sUrl,
+            'type' => $iType,
+            'direction' => BxDolLanguages::getInstance()->getLangDirection(),
+            'selected_profile' => (int)$iPersonToTalk,
+            'jot_id' => $iStartJot,
+            'by_url' => (int)($iJotId != BX_IM_EMPTY),
+			'block_version' => +$bBlockVersion,
+			'server_url' => $this->_oConfig-> CNF['SERVER_URL'],
+            'message_length' => (int)$CNF['MAX_SEND_SYMBOLS'],
+            'jot_jwt' => $sJotJWT ? $this->_oConfig->generateJWTToken($iProfileId, array('profile' => $iProfileId)) : '',
+            'ident' => md5(BX_DOL_URL_ROOT . BX_DOL_SECRET),
+			'ip' => gethostbyname($aUrlInfo['host']),
+			'embed_template' => $sEmbedTemplate,
+			'thumb_icon' => $this->parseHtmlByName('thumb-icon.html', []),
+			'thumb_letter' => $this->parseHtmlByName('thumb-letter.html', []),
+			'add_user_item' => $this->parseHtmlByName('add-user-item.html', []),
+			'max_history' => (int)$CNF['MAX_JOTS_BY_DEFAULT'],
+			'quill_toolbar' => json_encode($this->_oConfig->CNF['QUILL_TOOLBAR'] ?? ''),
+			'jitsi_server' => $this->_oConfig->getValidUrl($CNF['JITSI-SERVER'], 'url'),
+			'last_unread_jot' => $iLastUnreadJot,
+			'unread_jots' => $iUnreadJotsNumber,
+            'unqiue_mode' => (int)$CNF['USE-UNIQUE-MODE'],
+			'allow_attach' => +$bAttach,
+			'messages' => count($aUnreadJotsStat) ? json_encode($aUnreadJotsStat) : 0,
+			'muted' => ($iLotId && $iProfileId ? (int)$this->_oDb->isMuted($iLotId, $iProfileId) : 0),
+			'dates_intervals_template' => $this->parseHtmlByName('date-separator.html', array('date' => '__date__')),
+            'emoji_set' => $CNF['EMOJI_SET'],
+            'emoji_prev' => +$CNF['EMOJI_PREVIEW'],
+            'welcome_message' => $sWelcomeMessage,
+            'reaction_template' => $this->parseHtmlByName('reaction.html', array(
+			    'emoji_id' => '__emoji_id__',
+			    'on_click' => 'oMessenger.onRemoveReaction(this);',
+			    'parts' => '__parts__',
+			    'title' => _t('_bx_messenger_reaction_title_author'),
+                'number' => 1,
+                'count' => 1,
+                'value' => '__value__',
+                'params' => json_encode(array(
+                    'id' => '__emoji_id__',
+                    'size' => $CNF['REACTIONS_SIZE'],
+                    'native' => $CNF['EMOJI_SET'] === 'native',
+                    'set' => $CNF['EMOJI_SET']
+                ))
+            )),
+			'jot_template' => $this->getMembersJotTemplate($iProfileId),
+			'jot_url' => $this->_oConfig->getRepostUrl()
+		];
+
+        // init files Uploader
+        $oStorage = new BxMessengerStorage($this->_oConfig-> CNF['OBJECT_STORAGE']);
+        if ($oStorage) {
+            $sBaseUrl = $this->_oConfig->getBaseUri();
+            $aVars['files_uploader'] = json_encode(array(
+              'input_name' => $CNF['FILES_UPLOADER'],
+              'restricted_extensions' => json_encode($oStorage->getRestrictedExt()),
+              'uploader_url' => $sBaseUrl . 'upload_temp_file',
+              'remove_temp_file_url' => $sBaseUrl . 'upload_temp_file',
+              'file_size' => (int)$oStorage->getMaxUploadFileSize($iProfileId)/(1024*1024),// in bytes
+              'number_of_files' => (int)$this->_oConfig->CNF['MAX_FILES_TO_UPLOAD'],
+              'is_block_version' => +$bBlockVersion
+            ));
+        }
+
+		return $this -> parseHtmlByName('config.html', $aVars);
+	}
+	public function getJotReactions($iJotId){
+        $CNF = &$this->_oConfig->CNF;
+	    $aReactions = $this->_oDb->getJotReactions($iJotId);
+	    if (empty($aReactions))
+	        return '';
+
+	    $aJotReactions = array();
+        $iViewer = bx_get_logged_profile_id();
+        foreach($aReactions as &$aReaction) {
+            $aJotReactions[$aReaction[$CNF['FIELD_REACT_EMOJI_ID']]]['profiles'][$aReaction[$CNF['FIELD_REACT_PROFILE_ID']]] =
+                $aReaction[$CNF['FIELD_REACT_PROFILE_ID']] == $iViewer
+                    ? _t('_bx_messenger_reaction_title_author') : $this->getObjectUser($aReaction[$CNF['FIELD_REACT_PROFILE_ID']])->getDisplayName();
+
+            $aJotReactions[$aReaction[$CNF['FIELD_REACT_EMOJI_ID']]][$CNF['FIELD_REACT_NATIVE']] = $aReaction[$CNF['FIELD_REACT_NATIVE']];
+        }
+
+        $sReactions = '';
+        foreach($aJotReactions as $sEmojiId => $aItems) {
+            $aProfiles = $aItems['profiles'];
+            $iCount = count($aProfiles);
+            $sReactions .= $this->parseHtmlByName('reaction.html', [
+                'title' => _t('_bx_messenger_reaction_title', implode(', ', $aProfiles), $sEmojiId),
+                'emoji_id' => $sEmojiId,
+                'number' => $iCount,
+                'parts' => implode(',', array_keys($aProfiles)),
+                'count' => $iCount,
+                'on_click' => $iViewer ? 'oMessenger.onRemoveReaction(this);' : 'javascript:void(0);',
+                'value' => $aItems[$CNF['FIELD_REACT_NATIVE']]
+            ]);
+        }
+
+        return $sReactions;
+    }
+
+	public function getMessageIcons($iJotId, $sType = 'edit', $isAdmin = false)
+	{ 
+		$CNF = &$this->_oConfig->CNF;
+		$sContent = '';		
+		if (!($aJotInfo = $this -> _oDb -> getJotById($iJotId)))
+			return $sContent;
+		
+		$sDate = bx_process_output($aJotInfo[$CNF['FIELD_MESSAGE_LAST_EDIT']], BX_DATA_DATETIME_TS);
+		$sEditorName = $aJotInfo[$CNF['FIELD_MESSAGE_EDIT_BY']] ? $this -> getObjectUser($aJotInfo[$CNF['FIELD_MESSAGE_EDIT_BY']]) -> getDisplayName() : '';
+		
+		switch($sType)
+		{
+			case 'edit':
+				$sContent = $aJotInfo[$CNF['FIELD_MESSAGE_EDIT_BY']] ?
+								$this -> parseHtmlByName('edit-icon.html',
+									array(
+											'edit' => _t('_bx_messenger_edit_by', $sDate, $sEditorName)
+										)
+								) : '';
+				break;
+			case 'delete':
+				$sContent = $this -> parseHtmlByName('deleted_jot.html',
+						array(
+								'bx_if:allow_to_delete' => array(
+															'condition' => $isAdmin,
+															'content'	=>
+                                                                            array(
+                                                                                    'message' => bx_js_string(_t('_bx_messenger_confirm_delete_completely')),
+                                                                                    'id' => $iJotId
+                                                                                ),
+															),
+								'info' => $sEditorName ? _t('_bx_messenger_deleted_by', $sDate, $sEditorName) : ''
+							)
+					);
+				break;
+            case 'vc':
+                $iVC = $aJotInfo[$CNF['FIELD_MESSAGE_VIDEOC']];
+                $sContent = _t('_bx_messenger_jitsi_err_vc_was_not_found');
+                $sParticipants = '';
+
+                if ($iVC && ($aJVCItem = $this->_oDb->getJVCItem($iVC)))
+                {
+                    $aParticipants = explode(',', $aJVCItem[$CNF['FJVCT_PART']]);
+                    if (!$aJVCItem[$CNF['FJVCT_END']])
+                       $sInfo = _t('_bx_messenger_jitsi_has_started', bx_time_js($aJVCItem[$CNF['FJVCT_START']]));
+                    else
+                    {
+                            $iDiff = $aJVCItem[$CNF['FJVCT_END']] - $aJVCItem[$CNF['FJVCT_START']];
+                            $iH = floor( $iDiff / 3600 );
+                            $iM = floor( (int)( $iDiff / 60 ) % 60 );
+                            $iS = $iDiff % 60;
+
+                            $sDate = _t('_bx_messenger_jitsi_vc_duration_s', $iS);
+                            if ($iH)
+                                $sDate = _t('_bx_messenger_jitsi_vc_duration_h', $iH, $iM, $iS);
+                            else
+                                if ($iM)
+                                    $sDate = _t('_bx_messenger_jitsi_vc_duration_m', $iM, $iS);
+
+                            $aIcons = array();
+                            foreach($aParticipants as &$iProfileId) {
+                                if ($oProfile = BxDolProfile::getInstance($iProfileId))
+                                $aIcons[] = array(
+                                    'id' => $iProfileId,
+                                    'icon' => $oProfile->getIcon(),
+                                    'name' => $oProfile->getDisplayName(),
+                                );
+                            }
+
+                            $sParticipants = !empty($aIcons) ? $this -> parseHtmlByName('viewed.html', array(
+                                'bx_repeat:viewed' => $aIcons
+                            )) : '';
+
+                            $sInfo = _t('_bx_messenger_jitsi_conference', $sDate);
+                     }
+
+                        $aLotInfo = $this->_oDb->getLotByJotId($iJotId, false);
+                        $aJVC = $this->_oDb->getJVC($aLotInfo[$CNF['FIELD_ID']]);
+                        $sRoom = empty($aJVC) && !empty($aLotInfo) ? $this->_oConfig->getRoomId($aLotInfo[$CNF['FIELD_ID']], $aLotInfo[$CNF['FIELD_AUTHOR']]) : $aJVC[$CNF['FJVC_ROOM']];
+
+                        $sContent = $this -> parseHtmlByName('vc-message.html',
+                            array(
+                                'info' => $sInfo,
+                                'bx_if:join' => array(
+                                    'condition' => !$aJVCItem[$CNF['FJVCT_END']] && ($isAdmin || $this->_oConfig->isAllowedAction(BX_MSG_ACTION_JOIN_TALK_VC) === true),
+                                    'content' => array(
+                                        'id' => $aLotInfo[$CNF['FIELD_ID']],
+                                        'room' => $sRoom
+                                    )
+                                ),
+                                'bx_if:part' => array(
+                                    'condition' => $sParticipants,
+                                    'content' => array(
+                                       'participants' => $sParticipants
+                                   )
+                                )
+                            ));
+                }
+                break;
+		}
+		
+		return $sContent;
+	}
+	
+	/**
+	* Create profile html template for jot which is used when member posts a message
+	*@param int $iProfileId logget member id
+	*@return string html code
+	*/
+	public function getMembersJotTemplate($iProfileId){
+		if (!$iProfileId)
+		    return '';
+
+		$CNF = &$this->_oConfig->CNF;
+		$oProfile = $this -> getObjectUser($iProfileId);
+
+        $isAllowedDelete = $CNF['ALLOW_TO_REMOVE_MESSAGE'] || isAdmin();
+		if ($oProfile)
+		{
+			$aJot = [];
+            $sDisplayName = $oProfile->getDisplayName();
+            $sThumb = $oProfile->getThumb();
+            $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+
+		    $aVars['bx_repeat:jots'][] = array
+			(
+				'title' => $oProfile->getDisplayName(),
+        		'url' => $oProfile->getUrl(),
+				'thumb' => $oProfile->getThumb(),
+				'display' => 'style="display:flex;"',
+				'display_message' => 'style="display:none;"',
+				'id' => 0,
+				'new' => '',
+				'my' => 1,
+				'message' => '{message}',
+				'attachment' => '',
+				'reply' => $this -> parseHtmlByName('reply.html', array(
+                    'id' => '{reply_parent_id}',
+                    'message' => '{reply_message}'
+                )),
+                'bx_if:show_author' => array(
+                    'condition' => true,
+                    'content' => array(
+                        'url' => $oProfile->getUrl(),
+                        'bx_if:avatars' => array(
+                            'condition' => $bThumb,
+                            'content' => array(
+                                'thumb' => $sThumb,
+                                'title' => $sDisplayName,
+                            )
+                        ),
+                        'bx_if:letters' => array(
+                            'condition' => !$bThumb,
+                            'content' => array(
+                                'color' => implode(', ', BxDolTemplate::getColorCode($oProfile->id(), 1.0)),
+                                'letter' => mb_substr($sDisplayName, 0, 1)
+                            )
+                        ),
+                    )
+                ),
+                'bx_if:show_title' => array(
+                    'condition' => true,
+                    'content' => array(
+                        'title' => $sDisplayName,
+                    )
+                ),
+                'bx_if:jot_menu' => array(
+                    'condition' => true,
+                    'content' => array(
+                        'jot_menu' => $this->getJotMenuCode(BX_IM_EMPTY)
+                    )
+                ),
+                'bx_if:time-separator' => array(
+                    'condition' => false,
+                    'content' => array(
+                        'date' => '',
+                    )
+                ),
+				'thread_replies' => '',
+                'bx_if:show_reactions_area' => array(
+                    'condition' => true,
+                    'content' => array(
+                        'bx_if:reactions' => array(
+                            'condition' => true,
+                            'content' => array(
+                                'reactions' => '',
+                                'bx_if:reactions_menu' => array(
+                                    'condition' => true,
+                                    'content' => array(
+                                        'display' => 'none',
+                                    )
+                                ),
+                            )
+                        ),
+                        'bx_if:edit' => array(
+                            'condition' => $isAllowedDelete,
+                            'content'	=> array()
+                        ),
+                    )
+                ),
+				'bx_if:blink-jot' => array(
+					'condition' => false,
+					'content' => array()
+				),
+                'bx_if:new' => array(
+                    'condition' => true,
+                    'content' => array()
+                ),
+                'icons' => $this->parseHtmlByName('jot-icons.html', array(
+                                        'edit_icon' => '',
+                                        'time' => bx_time_js(time(), BX_FORMAT_TIME, true),
+                                        'views' => '',
+                                    )),
+				'edit_icon' => '',
+                'reactions' => '',
+				'action_icon' => '',
+                'view_in_chat' => '',
+                'message_class' => 'hidden'
+			);
+			return $this -> parseHtmlByName('jots.html',  $aVars);
+		}
+		
+		return '';
+	}
+
+    function videoPlayer ($sUrlPoster, $sUrlMP4, $sUrlWebM = '', $sUrlMP4Hd = '', $aAttrs = false, $bDynamic = true)
+    {
+        $oPlayer = BxDolPlayer::getObjectInstance();
+        if (!$oPlayer)
+            return '';
+
+        return $oPlayer->getCodeVideo (BX_PLAYER_STANDARD, array(
+            'poster' => $sUrlPoster,
+            'mp4' => array('sd' => $sUrlMP4, 'hd' => $sUrlMP4Hd),
+            'webm' => array('sd' => $sUrlWebM ),
+            'attrs' => $aAttrs,
+            'styles' => 'width:90%; max-width:480px; height:auto;',
+        ), $bDynamic);
+    }
+
+    function getAttachmentsVideoTranscoders($sStorage = ''){
+	    $aTranscoders = parent::getAttachmentsVideoTranscoders($sStorage);
+	    if (!$aTranscoders)
+            return array();
+
+        $aTranscoders['webm'] = BxDolTranscoderImage::getObjectInstance($this -> _oConfig -> CNF['OBJECT_VIDEOS_TRANSCODERS']['webm']);
+        return $aTranscoders;
+    }
+
+    function getVideoFilesToPlay($aFile){
+        $CNF = &$this -> _oConfig -> CNF;
+
+        $sFileUrl = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE'])->getFileUrlById((int)$aFile[$CNF['FIELD_ST_ID']]);
+        $aTranscodersVideo = $this -> getAttachmentsVideoTranscoders();
+        if (empty($aTranscodersVideo))
+            return '';
+
+        $sMp4File = $aTranscodersVideo['mp4']->getFileUrl((int)$aFile[$CNF['FIELD_ST_ID']]);
+        $sMp4HDFile = $aTranscodersVideo['mp4_hd']->getFileUrl((int)$aFile[$CNF['FIELD_ST_ID']]);
+        $sWebMFile = $aTranscodersVideo['webm']->getFileUrl((int)$aFile[$CNF['FIELD_ST_ID']]);
+        $sPoster = $aTranscodersVideo['poster']->getFileUrl($aFile[$CNF['FIELD_ST_ID']]);
+
+        if (!$sMp4File && !$sWebMFile){
+            if ($aFile[$CNF['FIELD_ST_EXT']] == 'webm') {
+                $sWebMFile = $sFileUrl;
+                $sPoster = '';
+            }
+            if ($aFile[$CNF['FIELD_ST_EXT']] == 'mp4' || $aFile[$CNF['FIELD_ST_EXT']] == 'mov') {
+                $sMp4File = $sMp4HDFile = $sFileUrl;
+                $sPoster = '';
+            }
+        }
+
+       return ($sMp4File || $sWebMFile) ? $this -> videoPlayer(
+                $sPoster,
+                $sMp4File,
+                $sWebMFile,
+                $sMp4HDFile,
+                array('preload' => 'metadata'),
+                true
+            ) : $this->parseHtmlByName('tmp_video.html', array('img' => $sPoster));
+    }
+
+	function getReplyPreview($iJotId){
+	    if (!$iJotId || !($aJot = $this->_oDb->getJotById($iJotId)))
+	        return '';
+
+        $CNF = &$this -> _oConfig -> CNF;
+        if (!empty($aJot))
+        {
+            $aAttachType = [];
+            if ($aJot[$CNF['FIELD_MESSAGE_AT_TYPE']])
+                $aAttachType = array($aJot[$CNF['FIELD_MESSAGE_AT_TYPE']] => $aJot[$CNF['FIELD_MESSAGE_AT']]);
+            else if ($aJot[$CNF['FIELD_MESSAGE_AT']])
+                $aAttachType = @unserialize($aJot[$CNF['FIELD_MESSAGE_AT']]);
+
+            $sAttachmentType = '';
+            foreach($aAttachType as $sType => $sValue){
+                if ($sType == BX_ATT_TYPE_REPLY && !$aJot[$CNF['FIELD_MESSAGE']])
+                    continue;
+
+                $sAttachmentType = $sType;
+                break;
+            }
+
+            if ((int)$aJot[$CNF['FIELD_MESSAGE_REPLY']])
+                $sAttachmentType = BX_ATT_TYPE_REPLY;
+
+            switch($sAttachmentType)
+            {               
+                case BX_ATT_TYPE_GIPHY:
+                    return '<img src="//media1.giphy.com/media/' . $aAttachType[$sAttachmentType] . '/giphy_s.gif" />';
+                case BX_ATT_TYPE_REPLY:
+                    return get_mb_substr(html2txt($aJot[$CNF['FIELD_MESSAGE']]), 0, $CNF['JOT-PREVIEW-TEXT-LENGTH']);
+                case BX_ATT_TYPE_FILES_UPLOADING:
+                case BX_ATT_TYPE_FILES:
+                    $aFiles = $this -> _oDb -> getJotFiles($aJot[$CNF['FIELD_MESSAGE_ID']]);
+                    $aTranscodersVideo = $this -> getAttachmentsVideoTranscoders();
+                    $oStorage = new BxMessengerStorage($CNF['OBJECT_STORAGE']);
+                    $oTranscoderMp3 = BxDolTranscoderAudio::getObjectInstance($CNF['OBJECT_MP3_TRANSCODER']);
+                    $aFile = current($aFiles);
+                    $isVideo = $aTranscodersVideo && (0 == strncmp('video/', $aFile['mime_type'], 6)) && $aTranscodersVideo['poster']->isMimeTypeSupported($aFile['mime_type']);
+                    if ($oStorage -> isImageFile($aFile[$CNF['FIELD_ST_TYPE']])) {
+                        $sPhotoThumb = "";
+                        if ($oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']))
+                            $sPhotoThumb = $oImagesTranscoder->getFileUrl((int)$aFile[$CNF['FIELD_ST_ID']]);
+
+                        $sFileUrl = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE'])->getFileUrlById((int)$aFile[$CNF['FIELD_ST_ID']]);
+                        return '<img src="' . ( $sPhotoThumb ?: $sFileUrl ) . '" />';
+                    }
+
+                    if ($isVideo)
+                         return '<img src="' . $aTranscodersVideo['poster']->getFileUrl($aFile[$CNF['FIELD_ST_ID']]) . '" />';
+
+                    if ($oTranscoderMp3 -> isMimeTypeSupported($aFile[$CNF['FIELD_ST_TYPE']])) {
+                        $sAudioFileName = !empty($aFile[$CNF['FIELD_ST_NAME']]) ? $aFile[$CNF['FIELD_ST_NAME']] : '...';
+                        return _t('_bx_messenger_reply_audio_file', $sAudioFileName);
+                    }
+					
+					return $this -> parseHtmlByName('file.html', array(
+																	'type' => $oStorage -> getFontIconNameByFileName($aFile[$CNF['FIELD_ST_NAME']]),
+																	'name' => $aFile[$CNF['FIELD_ST_NAME']],
+																	'file_type' => $aFile[$CNF['FIELD_ST_TYPE']],
+															   ));
+				 default:
+                     if ($sAttachmentType && ($aService = $this->_oDb->getLotAttachmentType($sAttachmentType))){
+                         if (!empty($aService['module']) && !empty($aService['method']) && BxDolRequest::serviceExists($aService['module'], $aService['method'])) {
+                             return BxDolService::call($aService['module'], $aService['method'], [$aAttachType[$sAttachmentType] ?? '', $aJot[$CNF['FIELD_MESSAGE_ID']], 'reply']);
+                         }
+                     }
+                     return html2txt($aJot[$CNF['FIELD_MESSAGE']]);
+            }
+        }
+	}
+    /**
+		 * Returns attachment according jot's attachment type
+		 * @param array $aJot jot info
+		 * @param bool $bIsDynamicallyLoad true when message is dynamically loaded to the history
+         * @param bool $bImplode implode attachments types to the groups to show in history
+		 * @return array/bool html code
+	 */
+    function getAttachment($aJot, $bIsDynamicallyLoad = false, $bImplode = true){
+		$iViewer = bx_get_logged_profile_id();
+		$CNF = &$this -> _oConfig -> CNF;
+
+        if (empty($aJot) || (empty($aJot[$CNF['FIELD_MESSAGE_AT']]) && !(int)$aJot[$CNF['FIELD_MESSAGE_REPLY']]))
+            return false;
+		
+		$bIsLotAuthor = $this -> _oDb -> isAuthor($aJot[$CNF['FIELD_MESSAGE_FK']], $iViewer);
+        if ($aJot[$CNF['FIELD_MESSAGE_AT_TYPE']])
+            $mixedValues = array($aJot[$CNF['FIELD_MESSAGE_AT_TYPE']] => $aJot[$CNF['FIELD_MESSAGE_AT']]);
+        else
+            $mixedValues = @unserialize($aJot[$CNF['FIELD_MESSAGE_AT']]);
+
+        $aResult = array();
+        foreach($mixedValues as $sType => $sValue) {
+            switch($sType)
+			{
+				case BX_ATT_TYPE_REPOST:
+				    $aResult[BX_ATT_TYPE_REPOST][] = $this -> getJotAsAttachment($sValue);
+					break;
+				case BX_ATT_TYPE_GIPHY:
+                    $aResult[BX_ATT_TYPE_GIPHY][] = $this -> parseHtmlByName('giphy.html', array(
+                        'gif' => $sValue,
+                        'time' => time(),
+                        'static' => $bIsDynamicallyLoad ? 'none' : 'flex',
+                        'dynamic' => $bIsDynamicallyLoad ? 'block' : 'none',
+                    ));
+                    break;
+                case BX_ATT_TYPE_FILES_UPLOADING:
+                case BX_ATT_TYPE_FILES:
+                        if (is_string($sValue))
+                            $aUploadingFilesList = explode(',', $sValue);
+                        else if (is_array($sValue))
+                            $aUploadingFilesList = $sValue;
+                        else
+                            $aUploadingFilesList = [];
+
+						$aFiles = $this -> _oDb -> getJotFiles($aJot[$CNF['FIELD_MESSAGE_ID']]);
+						$aItems = array(
+							'bx_repeat:images' => [],
+							'bx_repeat:files' => [],
+							'bx_repeat:videos' => [],
+							'bx_repeat:audios' => [],
+                            'bx_repeat:loading_placeholder' => []
+						);
+						
+						$aTranscodersVideo = $this -> getAttachmentsVideoTranscoders();
+						$oStorage = new BxMessengerStorage($this->_oConfig-> CNF['OBJECT_STORAGE']);
+						$oTranscoderMp3 = BxDolTranscoderAudio::getObjectInstance($this -> _oConfig -> CNF['OBJECT_MP3_TRANSCODER']);
+
+						foreach($aFiles as &$aFile)
+						{
+    						    if (($iKey = array_search($aFile[$CNF['FIELD_ST_NAME']], $aUploadingFilesList)) !== FALSE)
+    						        unset($aUploadingFilesList[$iKey]);
+
+    						    $bCollapsed = (bool)$aFile[$CNF['FJMT_COLLAPSED']];
+                                $isAllowedDelete = $this->_oDb->isAllowedToDeleteJot($aJot[$CNF['FIELD_MESSAGE_ID']], $iViewer, $aJot[$CNF['FIELD_MESSAGE_AUTHOR']], $bIsLotAuthor);
+    				            $isVideo = $aTranscodersVideo && (0 == strncmp('video/', $aFile['mime_type'], 6)) && $aTranscodersVideo['poster']->isMimeTypeSupported($aFile['mime_type']);
+								if ($oStorage -> isImageFile($aFile[$CNF['FIELD_ST_TYPE']]))
+								{
+								    $sPhotoThumb = '';
+									if ($aFile[$CNF['FIELD_ST_TYPE']] != 'image/gif' && $oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']))
+										$sPhotoThumb = $oImagesTranscoder->getFileUrl((int)$aFile[$CNF['FIELD_ST_ID']]);
+									
+									$sFileUrl = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE'])->getFileUrlById((int)$aFile[$CNF['FIELD_ST_ID']]);
+									$aItems['bx_repeat:images'][] = array(
+                                        'media_accordion' => $this->parseHtmlByName('media-accordion.html',[
+                                            'file_name' => $aFile[$CNF['FIELD_ST_NAME']],
+                                            'icon' => $oStorage -> getFontIconNameByFileName($aFile[$CNF['FIELD_ST_NAME']]),
+                                            'hidden' => +$bCollapsed,
+                                            'up_class' => $bCollapsed ? '' : 'hidden',
+                                            'down_class' => !$bCollapsed ? '' : 'hidden',
+                                        ]),
+										'hidden' => $bCollapsed ? 'hidden' : '',
+										'url' => $sPhotoThumb ? $sPhotoThumb : $sFileUrl,
+										'id' => $aFile[$CNF['FIELD_ST_ID']],
+										'name' => $aFile[$CNF['FIELD_ST_NAME']],
+										'file_menu' => $this -> getFileMenu($aFile[$CNF['FIELD_ST_ID']], $isAllowedDelete)
+									);
+								}
+							    elseif ($isVideo)
+								{
+									$aItems['bx_repeat:videos'][] = array(
+                                        'media_accordion' => $this->parseHtmlByName('media-accordion.html',[
+                                            'file_name' => $aFile[$CNF['FIELD_ST_NAME']],
+                                            'icon' => $oStorage -> getFontIconNameByFileName($aFile[$CNF['FIELD_ST_NAME']]),
+                                            'hidden' => +$bCollapsed,
+                                            'up_class' => $bCollapsed ? '' : 'hidden',
+                                            'down_class' => !$bCollapsed ? '' : 'hidden',
+                                        ]),
+                                        'hidden' => $bCollapsed ? 'hidden' : '',
+									    'file_name' => $aFile[$CNF['FIELD_ST_NAME']],
+										'id' => $aFile[$CNF['FIELD_ST_ID']],
+										'video' => $this -> getVideoFilesToPlay($aFile),
+										'file_menu' => $this -> getFileMenu($aFile[$CNF['FIELD_ST_ID']], $isAllowedDelete)
+									);
+
+								}
+                                elseif ($oTranscoderMp3 -> isMimeTypeSupported($aFile[$CNF['FIELD_ST_TYPE']]))
+                                {
+                                   $sFileUrl = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE'])->getFileUrlById((int)$aFile[$CNF['FIELD_ST_ID']]);
+                                   $sMp3File = $aFile[$CNF['FIELD_ST_EXT']] == 'mp3' ? $sFileUrl : $oTranscoderMp3->getFileUrl((int)$aFile[$CNF['FIELD_ST_ID']]);
+
+                                   $aItems['bx_repeat:audios'][] = array(
+                                       'media_accordion' => $this->parseHtmlByName('media-accordion.html',[
+                                           'file_name' => $aFile[$CNF['FIELD_ST_NAME']],
+                                           'icon' => $oStorage -> getFontIconNameByFileName($aFile[$CNF['FIELD_ST_NAME']]),
+                                           'hidden' => +$bCollapsed,
+                                           'up_class' => $bCollapsed ? '' : 'hidden',
+                                           'down_class' => !$bCollapsed ? '' : 'hidden',
+                                        ]),
+                                        'hidden' => $bCollapsed ? 'hidden' : '',
+                                        'id' => $aFile[$CNF['FIELD_ST_ID']],
+                                        'title' => $aFile[$CNF['FIELD_ST_NAME']],
+                                        'mp3' => $this -> audioPlayer($sMp3File, true),
+                                        'bx_if:loading' => array(
+                                            'condition' => !$sMp3File,
+                                            'content' => array(
+                                                'loading_img' => BxDolTemplate::getInstance()->getImageUrl('video-na.png')
+                                            )
+                                        ),
+                                        'file_menu' => $this -> getFileMenu($aFile[$CNF['FIELD_ST_ID']], $isAllowedDelete)
+									);
+								}
+								else
+									$aItems['bx_repeat:files'][] = array(
+																			'file' => $this -> parseHtmlByName('a-file.html',
+																															  [
+																																'file' => $this -> parseHtmlByName('file.html',
+                                                                                                                                   [
+																																			'type' => $oStorage -> getFontIconNameByFileName($aFile[$CNF['FIELD_ST_NAME']]),
+																																			'name' => $aFile[$CNF['FIELD_ST_NAME']],
+																																			'file_type' => $aFile[$CNF['FIELD_ST_TYPE']],
+                                                                                                                                            'bx_if:time' => [
+                                                                                                                                                'condition' => !$aJot[$CNF['FIELD_MESSAGE']],
+                                                                                                                                                'content' => [
+                                                                                                                                                'time' => bx_time_js($aFile[$CNF['FIELD_ST_ADDED']], BX_FORMAT_TIME, !$CNF['TIME-FROM-NOW']),
+                                                                                                                                                ]
+                                                                                                                                            ]
+																																    ]),
+                                                                                                                                    'id' => $aFile[$CNF['FIELD_MESSAGE_ID']],
+                                                                                                                                    'name' => $aFile[$CNF['FIELD_ST_NAME']],
+                                                                                                                                    'url' => BX_DOL_URL_ROOT
+																															    ]),
+																			'file_menu' => $this -> getFileMenu($aFile[$CNF['FIELD_MESSAGE_ID']], $isAllowedDelete)
+																		);								
+						}
+
+                        foreach($aUploadingFilesList as $sFileName)
+                            $aItems['bx_repeat:loading_placeholder'][] = array(
+                                'url' => $this->getImageUrl('audio-na.png'),
+                                'name' => $sFileName,
+                            );
+
+                        $aResult[BX_ATT_TYPE_FILES][] = $this -> parseHtmlByName('files.html', $aItems);
+						break;
+                default:
+                    if ($sType && $sValue && ($aService = $this->_oDb->getLotAttachmentType($sType))){
+                        if (!empty($aService['module']) && !empty($aService['method']) && BxDolRequest::serviceExists($aService['module'], $aService['method'])) {
+                            $mixedContent = BxDolService::call($aService['module'], $aService['method'], [$sValue, $aJot[$CNF['FIELD_MESSAGE_ID']]]);
+
+                            if (is_array($mixedContent) && !empty($mixedContent['content']))
+                                $aContent = $mixedContent;
+                            else
+                                $aContent = ['content' => $mixedContent];
+
+                            $aResult[BX_ATT_TYPE_CUSTOM][] = $this -> parseHtmlByName('custom-attachment.html', $aContent);
+                        }
+                    }
+            }
+        }
+
+        $aAttachments = [];
+        if ($bImplode) {
+            foreach ($CNF['IMPLODE_GROUPS'] as $sGroup => $aItems) {
+                foreach ($aResult as $sKey => $sValue) {
+                    if (in_array($sKey, $aItems)) {
+                        if (!isset($aAttachments[$sGroup]))
+                            $aAttachments[$sGroup] = '';
+
+                        $aAttachments[$sGroup] .= implode('', $sValue);
+                    } else
+                        $aAttachments[$sKey] = implode('', $sValue);
+                }
+            }
+        }
+
+        return $aAttachments;
+	}
+
+	public function getFileContent($aFile){
+        $CNF = &$this -> _oConfig -> CNF;
+
+        $oStorage = new BxMessengerStorage($this->_oConfig-> CNF['OBJECT_STORAGE']);
+        if ($oStorage -> isImageFile($aFile[$CNF['FIELD_ST_TYPE']]))
+        {
+            $sPhotoThumb = '';
+            if ($aFile[$CNF['FIELD_ST_TYPE']] != 'image/gif' && $oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']))
+                $sPhotoThumb = $oImagesTranscoder->getFileUrl((int)$aFile[$CNF['FIELD_ST_ID']]);
+
+            $sFileUrl = BxDolStorage::getObjectInstance($this->_oConfig-> CNF['OBJECT_STORAGE'])->getFileUrlById((int)$aFile[$CNF['FIELD_ST_ID']]);
+            return $this -> parseHtmlByName('img.html', array(
+                                                                        'url' => $sPhotoThumb ? $sPhotoThumb : $sFileUrl,
+                                                                        'name' => $aFile[$CNF['FIELD_ST_NAME']],
+                                                                        'id' => $aFile[$CNF['FIELD_ST_ID']]
+                                                                    ));
+        }
+
+        $aTranscodersVideo = $this -> getAttachmentsVideoTranscoders();
+        $isVideo = $aTranscodersVideo && (0 == strncmp('video/', $aFile['mime_type'], 6)) && $aTranscodersVideo['poster']->isMimeTypeSupported($aFile['mime_type']);
+        if ($isVideo)
+              return $this -> parseHtmlByName('video.html', array(
+                                                                            'id' => $aFile[$CNF['FIELD_ST_ID']],
+                                                                            'video' => $this->getVideoFilesToPlay($aFile),
+                                                                        ));
+
+        $oTranscoderMp3 = BxDolTranscoderAudio::getObjectInstance($this -> _oConfig -> CNF['OBJECT_MP3_TRANSCODER']);
+        if ($oTranscoderMp3 -> isMimeTypeSupported($aFile[$CNF['FIELD_ST_TYPE']]))
+        {
+                $sFileUrl = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE'])->getFileUrlById((int)$aFile[$CNF['FIELD_ST_ID']]);
+                $sMp3File = $aFile[$CNF['FIELD_ST_EXT']] == 'mp3' ? $sFileUrl : $oTranscoderMp3->getFileUrl((int)$aFile[$CNF['FIELD_ST_ID']]);
+
+                return $this -> parseHtmlByName('audio.html', array(
+                                                                    'id' => $aFile[$CNF['FIELD_ST_ID']],
+                                                                    'title' => $aFile[$CNF['FIELD_ST_NAME']],
+                                                                    'mp3' => $this -> audioPlayer($sMp3File, true)
+                                                                ));
+        }
+
+
+         return $this -> parseHtmlByName('a-file.html', [
+                                                                    'file' => $this -> parseHtmlByName('file.html', [
+                                                                        'type' => $oStorage -> getFontIconNameByFileName($aFile[$CNF['FIELD_ST_NAME']]),
+                                                                        'name' => $aFile[$CNF['FIELD_ST_NAME']],
+                                                                        'file_type' => $aFile[$CNF['FIELD_ST_TYPE']],
+                                                                    ]),
+                                                                    'id' => $aFile[$CNF['FIELD_MESSAGE_ID']],
+                                                                    'url' => BX_DOL_URL_ROOT,
+                                                                    'name' => $aFile[$CNF['FIELD_ST_NAME']],
+															   ]);
+    }
+
+	/**
+	* Returns Jot content as attachment(repost) for a message
+	*@param int $iJotId jot id
+	*@return string html code
+	*/
+	function getJotAsAttachment($iJotId){
+        $CNF = &$this->_oConfig->CNF;
+		$sHTML = '';
+
+		$aJot = $this -> _oDb -> getJotById($iJotId);
+		if (empty($aJot))
+			return $sHTML;
+
+		$iAttachedJotId = $this -> _oDb -> hasAttachment($iJotId);
+        if ($iAttachedJotId !== false)
+		{
+			$sOriginalMessage = $this->_oConfig->cleanRepostLinks($aJot[$CNF['FIELD_MESSAGE']], $iAttachedJotId);
+			if (!$sOriginalMessage)
+				$aJot = $this -> _oDb -> getJotById($iAttachedJotId);
+		}
+		
+	$sMessage = '';
+        if (isset($aJot[$CNF['FIELD_MESSAGE_AT']]) && $aJot[$CNF['FIELD_MESSAGE_AT']]) {
+            $aAttachment = $this->getAttachment($aJot);
+            if (isset($aAttachment[BX_ATT_GROUPS_ATTACH]))
+                $sMessage = $aJot[$CNF['FIELD_MESSAGE']] . $aAttachment[BX_ATT_GROUPS_ATTACH];
+        }
+		else
+			$sMessage = $this -> _oConfig -> bx_linkify($aJot[$CNF['FIELD_MESSAGE']]);
+		
+		if (!empty($aJot))
+		{
+			$aLotsTypes = $this -> _oDb -> getLotsTypesPairs();
+            $oProfile = $this -> getObjectUser($aJot[$CNF['FIELD_MESSAGE_AUTHOR']]);
+			$aLotInfo =  $this -> _oDb -> getLotByJotId($iJotId, false);
+			$sHTML = $this -> parseHtmlByName('repost.html', array(
+					'icon' => $oProfile -> getThumb(),
+					'message' => $sMessage,
+					'username' => $oProfile -> getDisplayName(),
+                    'message_type' => !empty($aLotInfo) && isset($aLotInfo[$CNF['FIELD_TYPE']])? _t('_bx_messenger_lots_message_type_' . $aLotsTypes[$aLotInfo[$CNF['FIELD_TYPE']]]) : '',
+                    'date' => bx_process_output($aJot[$CNF['FIELD_MESSAGE_ADDED']], BX_DATA_DATETIME_TS),
+            ));
+		}
+		
+		return $sHTML;
+	}
+	
+	/**
+	* Returns user profile even if it was removed from the site 
+	*@param int $iProfileId profile id 
+	*@return object instance of Profile
+	*/
+	public function getObjectUser($iProfileId)
+	{
+		bx_import('BxDolProfile');
+		$oProfile = BxDolProfile::getInstance($iProfileId);
+		if (!$oProfile)
+		{
+			bx_import('BxDolProfileUndefined');
+			$oProfile = BxDolProfileUndefined::getInstance();
+		}
+
+		return $oProfile;
+	}
+
+	/**
+	* Returns files uploading form
+	*@param int $iJotId message id
+	*@return string html form
+	*/
+	public function getEditJotArea($iJotId)
+	{
+		$aJot = $this -> _oDb -> getJotById($iJotId);
+		return $this -> parseHtmlByName('edit-jot.html', array(
+			'place_holder' => _t('_bx_messenger_post_area_message'),
+			'content' => $aJot[$this->_oConfig->CNF['FIELD_MESSAGE']]
+        ));
+	}
+
+	/**
+	* Returns body of the jot
+	*@param int $iJotId id of the jot
+	*@return string html form
+	*/	
+	public function getJotsBody($iJotId)
+	{
+		$aJot = $this -> _oDb -> getJotById($iJotId);
+		if (empty($aJot))
+			return '';
+
+        $sMessage = $this->_oConfig->bx_linkify($aJot[$this -> _oConfig -> CNF['FIELD_MESSAGE']]);
+        $aAttachment = $this -> getAttachment($aJot);
+		$aVars = array(
+			'message' => $sMessage,
+            'attachment' => isset($aAttachment[BX_ATT_GROUPS_ATTACH]) ? $aAttachment[BX_ATT_GROUPS_ATTACH] : ''
+		);
+		
+		return $this -> parseHtmlByName('hidden-jot.html',  $aVars);
+	}
+
+	/**
+	* Returns Video Recording form
+	*@param int $iProfile viewer profile id
+	*@return string html form
+	*/
+	public function getVideoRecordingForm(){
+		return $this -> parseHtmlByName('video-record-form.html', array('max_video_length' => (int)$this->_oConfig->CNF['MAX_VIDEO_LENGTH']  * 60 * 1000));
+	}
+
+    function audioPlayer($sUrlMP3, $bReturnBothIfEmpty = false, $aAttrs = false, $sStyles = '')
+    {
+        $aAttrsDefaults = array(
+            'controls' => '',
+            'loop' => '',
+            'preload' => 'metadata',
+            'download' => true
+        );
+
+        if ($bReturnBothIfEmpty && !$sUrlMP3)
+            unset($aAttrsDefaults['controls']);
+
+        $aAttrs = array_merge($aAttrsDefaults, is_array($aAttrs) ? $aAttrs : array());
+        $sAttrs = bx_convert_array2attrs($aAttrs, '', $sStyles);
+
+        $sLoading = '<img style="max-height:3rem;" src="' . $this->getImageUrl('audio-na.png') . '" />';
+
+        $sAudio = "<audio {$sAttrs}>
+                     " . ($sUrlMP3 ? '<source type="audio/mp3" src="' . $sUrlMP3 .'" />' : '') . "
+                   </audio>";
+
+        if ($bReturnBothIfEmpty && !$sUrlMP3)
+            return $sLoading . $sAudio;
+
+        return $sUrlMP3 ? $sAudio : $sLoading;
+    }
+
+    public function getGiphyPanel(){
+	    return $this -> parseHtmlByName('giphy-panel.html', [
+	        'bx_if:powered_by' => [
+	           'condition' => $this->_oConfig->CNF['GIPHY-POWERED-BY'],
+               'content' => []
+	        ]
+        ]);
+    }
+
+    public function getGiphyItems($sAction, $sQuery, $fHeight = 0, $iStart = 0){
+        $oResult = $this->_oConfig->getGiphyGifs($sAction, $sQuery, $iStart);
+        $fGifHeight = $fHeight/200;
+        $iTime = time();
+
+        $aResult = array('pagination' => array(), 'content' => '');
+        if ($oResult && ($aResult = json_decode($oResult, true))){
+            if (!empty($aResult['data'])){
+                $aVars['bx_repeat:gifs'] = array();
+                foreach($aResult['data'] as &$aGif) {
+                    $aImage = $aGif['images']['fixed_height'];
+                    $aVars['bx_repeat:gifs'][] = array(
+                        'id' => $aGif['id'],
+                        'width' => $aImage['width'] * $fGifHeight,
+                        'height' => $fHeight,
+                        'gif' => $aGif['id'],
+                        'time' => $iTime,
+                        'title' => $aGif['title']
+                    );
+                }
+
+                if (!empty($aVars['bx_repeat:gifs']))
+                    $aResult = array('pagination' => $aResult['pagination'], 'content' => $this->parseHtmlByName('giphy-items.html', $aVars));
+            }
+        }
+
+        return $aResult;
+    }
+
+    public function getJitsi($iLotId, $iProfileId, $aOptions){
+	    $CNF = &$this->_oConfig->CNF;
+        $JITSI = &$CNF['JITSI'];
+        $sError = '';
+
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        if (empty($aLotInfo))
+            $sError = MsgBox(_t('_bx_messenger_not_found'));
+
+        if (!$this->_oDb->isParticipant($iLotId, $iProfileId))
+            $sError = MsgBox(_t('_bx_messenger_not_participant'));
+
+        if ($sError)
+            return BxBaseFunctions::getInstance()->msgBox($sError, 2.5);
+
+        $sTitle = isset($aLotInfo[$CNF['FIELD_TITLE']]) && $aLotInfo[$CNF['FIELD_TITLE']]
+            ? $aLotInfo[$CNF['FIELD_TITLE']]
+            : $this -> getParticipantsNames($iProfileId, $iLotId);
+
+        $sTitle = _t($sTitle);
+
+        $oProfileInfo = BxDolProfile::getInstance($iProfileId);
+        $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+        $sLanguage = $oLanguage->getCurrentLangName(false);
+
+        $aJVC = $this->_oDb->getJVC($iLotId);
+        $sRoom = empty($aJVC) ? $this->_oConfig->getRoomId($aLotInfo[$CNF['FIELD_ID']], $aLotInfo[$CNF['FIELD_AUTHOR']]) : $aJVC[$CNF['FJVC_ROOM']];
+
+        $mixedJWT = $this->getJWTToken($sRoom, $iProfileId);
+        $sCode = $this -> parseHtmlByName('jitsi_video_form.html', array(
+            'id' => $iLotId,
+            'domain' => $this->_oConfig->getValidUrl($CNF['JITSI-SERVER']),
+            'lang' => $sLanguage,
+            'site_title' =>  bx_js_string(getParam('site_title')),
+            'lib_link' => $this->_oConfig->getValidUrl($CNF['JITSI-SERVER'], 'url') . '/' . $JITSI['LIB-LINK'],
+            'info_enabled' => +$CNF['JITSI-HIDDEN-INFO'],
+            'chat_enabled' => +$CNF['JITSI-CHAT'],
+            'chat_sync' => +$CNF['JITSI-CHAT-SYNC'],
+            'audio_only' => +isset($aOptions['audio_only']),
+            'show_watermark' => +$CNF['JITSI-ENABLE-WATERMARK'],
+            'watermark_url' => $CNF['JITSI-WATERMARK-URL'],
+            'support_link' => $CNF['JITSI-SUPPORT-LINK'],
+            'jitsi_meet_title' => bx_js_string(_t('_bx_messenger_jitsi_meet_app_title', getParam('site_title'))),
+            'user_name' => $oProfileInfo->getDisplayName(),
+            'me' => _t('_bx_messenger_jitsi_meet_me'),
+            'avatar' => $oProfileInfo->getAvatar(),
+            'name' => $sRoom,
+            'title' => bx_js_string(strmaxtextlen($sTitle)),
+            'jwt_token' => $mixedJWT !== false ? $mixedJWT : ''
+        ));
+
+        return $sCode;
+	}
+
+    public function getPublicJitsi($iProfileId, $sIdent, $sTitle = '', $sId = 'bx-messenger-jitsi', $aInterfaceConfig = array()){
+        $CNF = &$this->_oConfig->CNF;
+        $JITSI = &$CNF['JITSI'];
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if (empty($oProfile))
+            return false;
+
+        $sTitle = $sTitle ? $sTitle : $oProfile->getDisplayName();
+
+        $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+        $sLanguage = $oLanguage->getCurrentLangName(false);
+
+        $sRoom = $this->_oConfig->getRoomId($sIdent ? $sIdent : $iProfileId);        
+        $mixedJWT = $this->getJWTToken($sRoom, $iProfileId);
+        return $this -> parseHtmlByName('jitsi_public_video_form.html', array(
+            'domain' => $this->_oConfig->getValidUrl($CNF['JITSI-SERVER']),
+            'lang' => $sLanguage,
+            'site_title' => getParam('site_title'),
+            'lib_link' => $this->_oConfig->getValidUrl($CNF['JITSI-SERVER'], 'url') . '/' . $JITSI['LIB-LINK'],
+            'info_enabled' => +$CNF['JITSI-HIDDEN-INFO'],
+            'chat_enabled' => +$CNF['JITSI-CHAT'],
+            'chat_sync' => +$CNF['JITSI-CHAT-SYNC'],
+            'audio_only' => 0,
+            'show_watermark' => +$CNF['JITSI-ENABLE-WATERMARK'],
+            'watermark_url' => $CNF['JITSI-WATERMARK-URL'],
+            'support_link' => $CNF['JITSI-SUPPORT-LINK'],
+            'jitsi_meet_title' => bx_js_string(_t('_bx_messenger_jitsi_meet_app_title', getParam('site_title'))),
+            'user_name' => $oProfile->getDisplayName(),
+            'me' => _t('_bx_messenger_jitsi_meet_me'),
+            'avatar' => $oProfile->getAvatar(),
+            'name' => $sRoom,
+            'id' => $sId,
+            'title' => bx_js_string(strmaxtextlen($sTitle)),
+            'interface_config' => json_encode($aInterfaceConfig),
+            'jwt_token' => $mixedJWT !== false ? $mixedJWT : ''
+        ));
+    }
+
+    function getJWTToken($sRoom, $iProfileId){
+        $oProfileInfo = BxDolProfile::getInstance( $iProfileId );
+        if (empty($oProfileInfo) || !$sRoom)
+            return false;
+
+        $CNF = $this->_oConfig->CNF;
+        if (empty($CNF['JWT']['app_id']) || empty($CNF['JWT']['secret']))
+            return false;
+
+        // Encode Header to Base64Url String
+        $sHeader = json_encode(['alg' => 'HS256', 'typ' => 'JWT']);
+        $sHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($sHeader));
+
+        // Create token payload as a JSON string
+        $sPayload = json_encode(['context' =>
+            [
+                'user' => [
+                    'avatar' => $oProfileInfo->getThumb(),
+                    'name' => $oProfileInfo->getDisplayName(),
+                    'email' => $oProfileInfo->getAccountObject()->getEmail()
+                ],
+            ],
+            "aud" => $this->_oConfig->CNF['JITSI-SERVER'],
+            "iss" => $CNF['JWT']['app_id'],
+            "sub" => $this->_oConfig->CNF['JITSI-SERVER'],
+            "room" => $sRoom
+        ]);
+
+        $sPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($sPayload));
+
+        // Create Signature Hash
+        $sSignature = hash_hmac('sha256', $sHeader . "." . $sPayload, $CNF['JWT']['secret'], true);
+        $sSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($sSignature));
+
+        return "{$sHeader}.{$sPayload}.{$sSignature}";
+    }
+
+    public function getTalkFiles($iProfileId, $iLotId, $iStart = 0){
+        $CNF = &$this->_oConfig->CNF;
+        if (!$iLotId || !$iProfileId || !$this->_oDb->isParticipant($iLotId, $iProfileId))
+            return MsgBox(_t('_bx_messenger_no_permissions'));
+
+        $sContent = MsgBox(_t('_bx_messenger_txt_msg_no_results'));
+        $aFiles = $this -> _oDb -> getLotFiles($iLotId, $iStart, $CNF['PARAM_DEFAULT_TALK_FILES_NUM']);
+        if (!empty($aFiles)) {
+            $aFilesItems = array();
+            foreach ($aFiles as &$aValue) {
+                $oOwner = $this->getObjectUser($aValue[$CNF['FIELD_ST_AUTHOR']]);
+                $sThumb = $oOwner->getThumb();
+                $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+                $sDisplayName = $oOwner->getDisplayName();
+                $aFilesItems[] = array(
+                    'time' => bx_time_js($aValue[$CNF['FIELD_ST_ADDED']], BX_FORMAT_TIME, !$CNF['TIME-FROM-NOW']),
+                    'file' => $this->getFileContent($aValue),
+                    'url' => $oOwner->getUrl(),
+                    'id' => $aValue[$CNF['FIELD_ST_ID']],
+                    'username' => $sDisplayName,
+                    'bx_if:avatars' => array(
+                        'condition' => $bThumb,
+                        'content' => array(
+                            'thumb' => $sThumb,
+                            'title' => $sDisplayName,
+                        )
+                    ),
+                    'bx_if:letters' => array(
+                        'condition' => !$bThumb,
+                        'content' => array(
+                            'color' => implode(', ', BxDolTemplate::getColorCode($aValue[$CNF['FIELD_ST_AUTHOR']], 1.0)),
+                            'letter' => mb_substr($sDisplayName, 0, 1),
+                            'title' => $sDisplayName
+                        )
+                    ),
+                );
+            }
+
+            if (!empty($aFilesItems))
+                $sContent = $this->parseHtmlByName('files-feeds.html', array('bx_repeat:files' => $aFilesItems));
+        }
+
+        return $iStart && !$sContent ? '' : $sContent;
+    }
+
+    public function getCallPopup($iLotId, $iProfileId){
+        $CNF = &$this->_oConfig->CNF;
+	    $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+        $aActiveVC = $this->_oDb->getActiveJVCItem($iLotId);
+        $oProfile = $this -> getObjectUser($aActiveVC[$CNF['FJVCT_AUTHOR_ID']]);
+        if (empty($aLotInfo) || !$this->_oDb->isParticipant($iLotId, $iProfileId) || empty($aActiveVC) || (int)$iProfileId == (int)$aActiveVC[$CNF['FJVCT_AUTHOR_ID']] || !$oProfile)
+            return false;
+
+        $sTitle = isset($aLotInfo[$CNF['FIELD_TITLE']]) && $aLotInfo[$CNF['FIELD_TITLE']]
+                ? _t($aLotInfo[$CNF['FIELD_TITLE']])
+                : $this -> getParticipantsNames($iProfileId, $iLotId);
+
+
+        $aJVC = $this->_oDb->getJVC($iLotId);
+        if (empty($aJVC))
+            return MsgBox(_t('_bx_messenger_jitsi_err_vc_was_not_found'));
+
+        $aVars = array(
+            'title' => $sTitle,
+            'thumb' => $oProfile -> getThumb(),
+            'id' => $iLotId,
+            'room' => $aJVC[$CNF['FJVC_ROOM']]
+        );
+
+	    $sContent = $this->parseHtmlByName('conference_call.html', $aVars);
+        return BxTemplFunctions::getInstance()->transBox('bx-messenger-vc-call', $sContent);
+    }
+
+    /**
+     * Talk settings: available options and the selected ones (all of them when nothing is saved yet).
+     * @param int $iLotId
+     * @return array ['options' => [['key' => ..., 'value' => ...], ...], 'values' => [...]]
+     */
+    public function getLotSettingsOptions($iLotId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aSettingsOptions = [];
+        $aSelectedValues = [];
+        $aLotSettings = $this->_oDb->getLotSettings($iLotId);
+        foreach ($CNF['LOT_OPTIONS'] as $sValue) {
+            $aSettingsOptions[] = ['key' => $sValue, 'value' => _t("_bx_messenger_lot_options_item_{$sValue}")];
+            if ($aLotSettings === false || in_array($sValue, $aLotSettings)) {
+                $aSelectedValues[] = $sValue;
+            }
+        }
+
+        bx_alert($this->_oConfig->getObject('alert'), 'talk_settings_before', $iLotId, $iLotId, [
+            'options' => &$aSettingsOptions,
+            'values' => &$aSelectedValues
+        ]);
+
+        return ['options' => $aSettingsOptions, 'values' => $aSelectedValues];
+    }
+
+    public function getLotSettingsForm($iLotId){
+        $CNF = &$this->_oConfig->CNF;
+        $aLotInfo = $this->_oDb->getLotInfoById($iLotId);
+
+        if (empty($aLotInfo) || empty($CNF['LOT_OPTIONS']))
+            $sContent = MsgBox(_t('_Empty'));
+        else
+        {
+            ['options' => $aSettingsOptions, 'values' => $aSelectedValues] = $this->getLotSettingsOptions($iLotId);
+
+            $aForm = [
+                'form_attrs' => [
+                    'name' => 'lot_options',
+                    'method' => 'post',
+                    'enctype' => 'multipart/form-data'
+                ],
+                'inputs' => [
+                    'options' => [
+                        'type' => 'checkbox_set',
+                        'name' => 'options',
+                        'value' => $aSelectedValues,
+                        'values' => $aSettingsOptions
+                    ],
+                    'buttons' => [
+                        'type' => 'input_set',
+                        'attrs_wrapper' => ['class' => 'bx-messenger-options-buttons'],
+                        [
+                            'type' => 'button',
+                            'name' => 'save',
+                            'value' => _t("_bx_messenger_save_button"),
+                            'attrs' => array('onclick' => "javascript:{$CNF['JSMain']}.onSaveLotSettings(this);")
+                        ],
+                        [
+                            'type' => 'button',
+                            'name' => 'close',
+                            'value' => _t("_bx_messenger_cancel_button"),
+                            'attrs' => array('onclick' => "javascript:$(this).closest('.bx-popup-applied:visible').dolPopupHide();")
+                        ],
+                    ]
+                ]
+            ];
+
+            $oForm = new BxTemplFormView($aForm);
+            $sContent = $oForm -> getCode();
+        }
+
+        return BxTemplFunctions::getInstance()->popupBox('lot-settings', _t('_bx_messenger_lot_options_title'), $sContent);
+    }
+
+	public function getNavGroupsMenu($iProfileId){
+        $CNF = $this->_oConfig->CNF;
+
+        $aGroupsList = $this->_oDb->getMyLotsByGroups($iProfileId);
+        $aResultGroups = array();
+
+        $i = 0;
+        foreach($aGroupsList as $sModule => $aGroups){
+			$aResultGroups['bx_repeat:groups'][$i] = array(
+                'module' => _t('_' . $sModule),
+				'module_type' => $sModule,
+            );
+			foreach($aGroups as &$aGroup){
+				$aResultGroups['bx_repeat:groups'][$i]['bx_repeat:module_items'][] = array(
+					'title' => $aGroup[$CNF['FMG_NAME']],
+					'id' => $aGroup[$CNF['FMG_ID']],
+				);
+			}
+			
+			$i++;
+        }
+	
+        if (empty($aResultGroups))
+            return '';
+
+        return $this -> parseHtmlByName('nav-groups-menu.html', $aResultGroups);
+    }
+
+    public function getLeftMainMenu(){
+        $CNF = &$this->_oConfig->CNF;
+
+        $oLeftMainMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_NAV_LEFT_MENU']);
+        $oLeftGroupsMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_GROUPS_MENU']);
+
+        $bSimpleMode = $this->_oConfig->CNF['USE-UNIQUE-MODE'];
+        return bx_is_api() ?  [
+                                'menu' => $oLeftMainMenu->getMenuItems(),
+                                'nav_groups_menu' => $oLeftGroupsMenu->getMenuItems()
+                            ] :
+                            $this -> parseHtmlByName('left-nav-menu.html', array(
+                                'menu' => $oLeftMainMenu -> getCode(),
+                                'nav_groups_menu' => $oLeftGroupsMenu -> getCode(),
+                                'js_code' => $CNF['JSMain'],
+                                'menu_width' => !$bSimpleMode ? 'xl:block xl:col-span-2': ''
+                            ));
+    }
+
+    public function getInfoSection($sType = 'info', $iProfileId = 0){
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this -> parseHtmlByName('info-section.html', array(
+            'info' => ''
+        ));
+    }
+
+    public function getCreateGroupsForm($iProfileId, $iGroupId = 0){
+        $CNF = &$this->_oConfig->CNF;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_GROUPS']);
+        $aPrivacy = $oPrivacy->getGroupChooser($CNF['OBJECT_PRIVACY_GROUPS'], $iProfileId);
+
+        $aForm = array(
+            'form_attrs' => array(
+                'name' => 'create_groups',
+                'method' => 'post',
+                'enctype' => 'multipart/form-data'
+            ),
+            'inputs' => array(
+                'id' => array(
+                    'type' => 'hidden',
+                    'name' => 'id',
+                    'value' => $iGroupId,
+                ),
+                'name' => array(
+                    'type' => 'text',
+                    'name' => 'name',
+                    'caption' => _t('_bx_messenger_groups_name'),
+                    'info' => _t('_bx_messenger_groups_name_info'),
+                ),
+                'desc' => array(
+                    'type' => 'text',
+                    'name' => 'desc',
+                    'caption' => _t('_bx_messenger_groups_desc'),
+                    'info' => _t('_bx_messenger_groups_desc_info'),
+                ),
+                'privacy' => $aPrivacy,
+                'options' => array(
+                    'type' => 'input_set',
+                    'dv' => '<br/>'
+                ),
+                'buttons' => array(
+                    'type' => 'input_set',
+                    'attrs_wrapper' => array('class' => 'bx-messenger-options-buttons'),
+                    array(
+                        'type' => 'button',
+                        'name' => 'save',
+                        'value' => _t("_bx_messenger_save_button"),
+                        'attrs' => array('onclick' => "javascript:{$CNF['JSMessengerLib']}.onSaveGroup(this);")
+                    ),
+                    array(
+                        'type' => 'button',
+                        'name' => 'close',
+                        'value' => _t("_bx_messenger_cancel_button"),
+                        'attrs' => array('onclick' => "javascript:$(this).closest('.bx-popup-applied:visible').dolPopupHide();")
+                    ),
+                )
+            )
+        );
+
+
+        $oForm = new BxTemplFormView($aForm);
+        $sContent = $oForm -> getCode();
+
+        return BxTemplFunctions::getInstance()->popupBox('groups-form', _t('_bx_messenger_groups_create_form_title'), $sContent);
+    }
+
+    function getTalksList($iLotId){
+        $CNF = &$this->_oConfig->CNF;
+        $sList = _t('_Empty');
+
+        $iProfileId = bx_get_logged_profile_id();
+		if ($iGroupId = $this->_oDb->getGroupIdByLotId($iLotId)){
+			$aGroupsList = $this->_oDb->getTalksByGroupId($iGroupId);
+			if (!empty($aGroupsList)){
+				unset($aGroupsList[$iLotId]);
+
+				foreach($aGroupsList as $iKey => $aItem) {
+                    if (!$this->_oDb->isParticipant($aGroupsList[$iKey][$CNF['FIELD_ID']], $iProfileId) && (int)$aGroupsList[$iKey][$CNF['FIELD_TYPE']] !== BX_IM_TYPE_PUBLIC)
+                        continue;
+
+                    $aGroupsList[$iKey][$CNF['FIELD_TITLE']] = _t($aItem[$CNF['FIELD_TITLE']]);
+                }
+
+				$sList = $this->parseHtmlByName('groups-list-item.html', array(
+					'bx_repeat:list' => $aGroupsList
+				));
+			}
+		}
+		
+		return $this->parseHtmlByName('groups-list.html', array(
+		    'list' => $sList
+        ));
+	}
+
+	function getThreadReply($iJotId){
+        $CNF = &$this->_oConfig->CNF;
+        $aParentLotInfo = $this->_oDb->getLotByParentId($iJotId);
+        $iReplies = !empty($aParentLotInfo) ? $this->_oDb->getJotReplies($aParentLotInfo[$CNF['FIELD_ID']], true) : 0;
+
+        return $iReplies ? $this->parseHtmlByName('thread-reply.html', array(
+            'replies' => $iReplies
+        )) : '';
+    }
+
+    function getContacts($iProfileId, $aParams = []){
+        $aLotsList = $this->_oDb->getMyLots($iProfileId, ['type' => BX_IM_TYPE_PRIVATE]);
+        if (empty($aLotsList))
+            return [];
+
+        $CNF = &$this->_oConfig->CNF;
+        $aVars = $aResult = $aContacts = [];
+        foreach($aLotsList as &$aItem){
+            $aList = explode(',', $aItem[$CNF['FIELD_PARTICIPANTS']]);
+            if (!empty($aList)) {
+                foreach ($aList as &$iPart) {
+                    if ($iPart === $iProfileId || array_key_exists($iPart, $aContacts))
+                        continue;
+
+                    $aContacts[$iPart] = BxDolProfile::getInstance($iPart)->isOnline();
+                }
+            }
+
+            if (count($aContacts) >= $CNF['PARAM_CONTACTS_NUM_BY_DEFAULT'])
+                break;
+        }
+
+        foreach($aContacts as $iProfileId => $iStatus) {
+            $oProfile = BxDolProfile::getInstance($iProfileId);
+            if (bx_is_api())
+                $aResult[] = $oProfile->getData($iProfileId);
+            else
+            {
+                $sThumb = $oProfile->getThumb();
+                $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+                $sTitle = $oProfile->getDisplayName();
+                $aVars['bx_repeat:contacts'][] = [
+                    'status' => $this->getOnlineStatus($iProfileId, $iStatus),
+                    'url' => $oProfile->getUrl(),
+                    'displyname' => $oProfile->getDisplayName(),
+                    'bx_if:avatars' => [
+                        'condition' => $bThumb,
+                        'content' => [
+                            'title' => $sTitle,
+                            'thumb' => $sThumb,
+                        ]
+                    ],
+                    'bx_if:letters' => [
+                        'condition' => !$bThumb,
+                        'content' => [
+                            'color' => implode(', ', BxDolTemplate::getColorCode($iProfileId, 1.0)),
+                            'letter' => mb_substr($sTitle, 0, 1)
+                        ]
+                    ],
+                ];
+            }
+        }
+
+        return bx_is_api() ? $aResult : $this->parseHtmlByName('contacts-block.html', $aVars);
+    }
+
+    function getConnectionsForm($sType, $iProfileId){
+        $sText = MsgBox(_t("_bx_messenger_filter_criteria_on_$sType"));
+        if ($oConnection = $this->_oConfig->getConnectionByType($sType)) {
+            $sText = MsgBox(_t("_bx_messenger_filter_criteria_on_$sType"));
+            if ($iCount = $oConnection->getConnectedContentCount($iProfileId, $sType === 'friends'))
+                $sText = MsgBox(_t("_bx_messenger_filter_criteria_{$sType}_message", $iCount));
+        }
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $aNotifFields = $this->getNotificationFormData();
+	    $aForm = [
+                    'form_attrs' => [
+                        'method' => 'post',
+                        'id' => $CNF['OBJECT_FORM_ENTRY'],
+                        'class' => 'space-y-4 max-h-60 overflow-y-auto'
+                    ],
+                    'inputs' => array_merge([
+                        'type' => [
+                            'type' => 'hidden',
+                            'name' => 'connection_type',
+                            'value' => $sType
+                        ],
+                        'action' => [
+                            'type' => 'custom',
+                            'content' => $sText,
+                        ]
+                    ], $aNotifFields)
+                ];
+
+        $oForm = new BxTemplFormView($aForm);
+        return $oForm -> getCode();
+    }
+
+    function getInfoBlockContent($iLotId){
+        if (!($aLotInfo = $this->_oDb->getLotInfoById($iLotId)))
+            return '';
+
+        $CNF = $this->_oConfig->CNF;
+        if (!($oProfile = $this->getObjectUser($aLotInfo[$CNF['FIELD_AUTHOR']])))
+            return '';
+
+        $sThumb = $oProfile->getAvatar();
+        $bThumb = stripos($sThumb, 'no-picture') === FALSE;
+        $sDisplayName = $oProfile->getDisplayName();
+
+        $aPartList = $this->_oDb->getParticipantsList($iLotId);
+        if ((int)$aLotInfo[$CNF['FIELD_TYPE']] === BX_IM_TYPE_BROADCAST)
+            $aPartList = $this->_oDb->getBroadcastParticipants($iLotId);
+
+        $iPartCount = count($aPartList);
+        $iFilesCount = $this->_oDb->getLotFilesCount($iLotId);
+        $iMessagesCount = $this->_oDb->getJotsNumber($iLotId, 0);
+
+        $aItem = [
+          'parts' => $iPartCount,
+          'files' => $iFilesCount,
+          'messages' => $iMessagesCount,
+          'profile_url' => $oProfile->getUrl()
+        ];
+
+        $aItem = array_merge($aItem,[
+            'name' => $sDisplayName,
+            'id' => $oProfile -> id(),
+            'bx_if:avatars' => array(
+                'condition' => $bThumb,
+                'content' => array(
+                    'thumb' => $sThumb,
+                    'title' => $sDisplayName,
+                )
+            ),
+            'bx_if:letters' => array(
+                'condition' => !$bThumb,
+                'content' => array(
+                    'color' => implode(', ', BxDolTemplate::getColorCode($aLotInfo[$CNF['FIELD_AUTHOR']], 1.0)),
+                    'title' => $sDisplayName,
+                    'letter' => mb_substr($sDisplayName, 0, 1)
+                )
+            )
+        ]);
+
+        $sContent = '';
+        bx_alert($this->_oConfig->getObject('alert'), 'talk_info_before', $iLotId, $iLotId, [
+            'content' => &$sContent
+        ]);
+        
+        $aItem['content'] = $sContent;
+        return $this->parseHtmlByName('talk-info.html', $aItem);
+    }
+
+
+    private function getEmptyMessageTemplate($aData){
+        $aVars = [
+                'title' => '',
+                'url' => '',
+                'thumb' => '',
+                'display' => '',
+                'display_message' => '',
+                'id' => 0,
+                'new' => '',
+                'my' => 1,
+                'message' => '',
+                'attachment' => '',
+                'reply' => '',
+                'bx_if:show_author' => [
+                    'condition' => false,
+                    'content' => [
+                        'url' => '',
+                        'bx_if:avatars' => [
+                            'condition' => false,
+                            'content' => [
+                                'thumb' => '',
+                                'title' => '',
+                            ]
+                        ],
+                        'bx_if:letters' => array(
+                            'condition' => false,
+                            'content' => array(
+                                'color' => '',
+                                'letter' => ''
+                            )
+                        ),
+                    ]
+                ],
+                'bx_if:show_title' => array(
+                    'condition' => false,
+                    'content' => array(
+                        'title' => '',
+                    )
+                ),
+                'bx_if:jot_menu' => array(
+                    'condition' => false,
+                    'content' => array(
+                        'jot_menu' => ''
+                    )
+                ),
+                'bx_if:time-separator' => array(
+                    'condition' => false,
+                    'content' => array(
+                        'date' => '',
+                    )
+                ),
+                'thread_replies' => '',
+                'bx_if:show_reactions_area' => array(
+                    'condition' => false,
+                    'content' => array(
+                        'bx_if:reactions' => array(
+                            'condition' => false,
+                            'content' => array(
+                                'reactions' => '',
+                                'bx_if:reactions_menu' => array(
+                                    'condition' => false,
+                                    'content' => array(
+                                        'display' => 'none',
+                                    )
+                                ),
+                            )
+                        ),
+                        'bx_if:edit' => array(
+                            'condition' => false,
+                            'content'	=> array()
+                        ),
+                    )
+                ),
+                'bx_if:blink-jot' => array(
+                    'condition' => false,
+                    'content' => array()
+                ),
+                'bx_if:new' => array(
+                    'condition' => false,
+                    'content' => []
+                ),
+                'icons' => '',
+                'edit_icon' => '',
+                'reactions' => '',
+                'action_icon' => '',
+                'view_in_chat' => '',
+                'message_class' => 'hidden'
+            ];
+
+        return array_merge($aVars, $aData);
+    }
+}
+
+/** @} */

+ 128 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/config.php

@@ -0,0 +1,128 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Messenger Messenger
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+/**
+* Main Section.
+*/
+	'type' => BX_DOL_MODULE_TYPE_MODULE,
+	'name' => 'bx_messenger',
+	'title' => 'Messenger',
+	'note' => 'Messenger, instant messages, chat',
+	'version' => '15.0.0',
+	'vendor' => 'BoonEx',
+	'help_url' => 'http://feed.una.io/?section={module_name}',
+
+	'compatible_with' => array(
+		'15.0.0-RC3'
+	),
+
+	/**
+	* 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+	*/
+	'home_dir' => 'boonex/messenger/',
+	'home_uri' => 'messenger',
+
+	'db_prefix' => 'bx_messenger_',
+	'class_prefix' => 'BxMessenger',
+
+	/**
+	* Category for language keys.
+	 */
+	'language_category' => 'Messenger',
+
+	/**
+	* List of page triggers.
+	 */
+	'page_triggers' => array (
+	'trigger_page_profile_view_entry',
+	'trigger_page_group_view_entry',
+	),  
+
+	/**
+	*Menu triggers.
+	*/
+	'menu_triggers' => array(
+		'trigger_profile_view_submenu', 
+		'trigger_profile_view_actions',
+	),
+
+	/**
+     * Storage objects to automatically delete files from upon module uninstallation.
+     * Note. Don't add storage objects used in transcoder objects.
+     */
+	'storages' => array(
+		'bx_messenger_files',
+		'bx_messenger_photos_resized',
+		'bx_messenger_videos_processed',
+		'bx_messenger_mp3_processed'
+	),
+
+	/**
+	* Transcoders.
+	*/
+	
+	'transcoders' => array(
+        'bx_messenger_preview',
+		'bx_messenger_videos_poster',
+		'bx_messenger_videos_mp4',
+		'bx_messenger_videos_mp4_hd',
+		'bx_messenger_videos_webm',
+		'bx_messenger_mp3',
+	),
+	
+	/**
+	* Installation/Uninstallation Section.
+	*/
+	'install' => array(
+		'execute_sql' => 1,
+		'update_languages' => 1,
+		'clear_db_cache' => 1,
+	),
+	'uninstall' => array (
+		'execute_sql' => 1,
+		'update_languages' => 1,
+		'clear_db_cache' => 1,
+        'update_relations' => 1
+	),
+	'enable' => array(
+		'execute_sql' => 1,
+		'clear_db_cache' => 1,
+        'update_relations' => 1
+	),
+	'enable_success' => array(
+		'process_page_triggers' => 1,
+		'register_transcoders' => 1,
+		'process_menu_triggers' => 1,
+		'clear_db_cache' => 1,
+	),
+	'disable' => array (
+		'execute_sql' => 1,
+		'unregister_transcoders' => 1,
+		'clear_db_cache' => 1,
+        'update_relations' => 1
+	),
+	'disable_failed' => array (
+		'register_transcoders' => 1,
+		'clear_db_cache' => 1,
+	),
+
+	/**
+	 * Dependencies Section
+	 */
+	'dependencies' => array(),
+
+    'relations' => array(
+        'bx_notifications'
+    ),
+);
+
+/** @} */

+ 444 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/langs/en.xml

@@ -0,0 +1,444 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string name="_bx_messenger"><![CDATA[Messenger]]></string>
+	<string name="_bx_messenger_page_title_sys_view"><![CDATA[Messenger]]></string>
+	<string name="_bx_messenger_page_title_view"><![CDATA[Messenger]]></string>
+	<string name="_bx_messenger_page_block_title_post"><![CDATA[Post to Messenger]]></string>
+	<string name="_bx_messenger_page_block_title_view"><![CDATA[Messenger]]></string>
+	<string name="_bx_messenger_page_block_title_messenger"><![CDATA[Messenger]]></string>
+	<string name="_bx_messenger_txt_msg_no_results"><![CDATA[Empty]]></string>
+	<string name="_bx_messenger_menu_item_title_messenger"><![CDATA[Messenger]]></string>	
+	<string name="_bx_messenger_page_title_sys_home"><![CDATA[Messenger Page]]></string>
+	<string name="_bx_messenger_page_title_home"><![CDATA[My Messages]]></string>	
+	<string name="_bx_messenger_page_title_sys_main"><![CDATA[Messenger]]></string>
+	<string name="_bx_messenger_page_title_main"><![CDATA[Messenger]]></string>	
+	<string name="_bx_messenger_page_inbox_block_title"><![CDATA[Talks]]></string>	
+	<string name="_bx_messenger_page_conversation_block_title"><![CDATA[Messages]]></string>	
+	<string name="_bx_messenger_page_block_title"><![CDATA[Messenger]]></string>
+	<string name="_bx_messenger_symbols_num_option"><![CDATA[Maximum message's length (symbols number)]]></string>
+	<string name="_bx_messenger_send_message_save_error"><![CDATA[Message was not sent!]]></string>
+	<string name="_bx_messenger_send_message_no_data"><![CDATA[No data to send!]]></string>
+	<string name="_bx_messenger_lots_type_all"><![CDATA[All]]></string>	
+	<string name="_bx_messenger_symbols_num_brief_jot"><![CDATA[Message's length in Talk's preview]]></string>
+	<string name="_bx_messenger_post_area_message"><![CDATA[Message ...]]></string>
+	<string name="_bx_messenger_title_search"><![CDATA[Search..]]></string>
+	<string name="_bx_messenger_not_logged"><![CDATA[Login first please!]]></string>
+	<string name="_bx_messenger_not_found"><![CDATA[Talk is not found!]]></string>
+	<string name="_bx_messenger_jot_number_default"><![CDATA[Messages number in messenger history by default]]></string>
+	<string name="_bx_messenger_what_do_think"><![CDATA[Leave a first message]]></string>
+	<string name="_bx_messenger_post_button_title"><![CDATA[Send]]></string>
+	<string name="_bx_messenger_menu_item_title_system_messenger"><![CDATA[Messenger]]></string>	
+	<string name="_bx_messenger_menu_notifications_item_sys_title"><![CDATA[Messenger]]></string>
+	<string name="_bx_messenger_menu_notifications_item_title"><![CDATA[Messenger]]></string>	
+	<string name="_bx_messenger_name_place_holder"><![CDATA[Type name here...]]></string>
+	<string name="_bx_messenger_typing_message"><![CDATA[typing a message...]]></string>
+	<string name="_bx_messenger_linked_title"><![CDATA[Page: {0}]]></string>
+	<string name="_bx_messenger_lots_type_public"><![CDATA[General]]></string>
+	<string name="_bx_messenger_lots_type_private"><![CDATA[Private]]></string>
+	<string name="_bx_messenger_lots_type_sets"><![CDATA[Sets]]></string>
+	<string name="_bx_messenger_lots_type_events"><![CDATA[Events]]></string>
+	<string name="_bx_messenger_lots_type_groups"><![CDATA[Groups]]></string>
+	<string name="_bx_messenger_lots_menu_add_part"><![CDATA[Edit participants list]]></string>
+	<string name="_bx_messenger_lots_menu_leave"><![CDATA[Leave]]></string>
+	<string name="_bx_messenger_lots_menu_delete"><![CDATA[Delete]]></string>
+	<string name="_bx_messenger_lots_menu_mute"><![CDATA[Mute]]></string>
+	<string name="_bx_messenger_lots_menu_unmute"><![CDATA[Unmute]]></string>
+	<string name="_bx_messenger_max_jot_number_in_history"><![CDATA[Number of records loads to the history]]></string>
+	<string name="_bx_messenger_lots_menu_mute_info_off"><![CDATA[Turn off notifications]]></string>
+	<string name="_bx_messenger_lots_menu_mute_info_on"><![CDATA[Turn on notifications]]></string>
+	<string name="_bx_messenger_lots_menu_star_on"><![CDATA[Mark as important]]></string>
+	<string name="_bx_messenger_lots_menu_star_off"><![CDATA[Unmark talk]]></string>
+	<string name="_bx_messenger_lots_private_lot_info"><![CDATA[Open for anyone who access to {0}]]></string>
+	<string name="_bx_messenger_lots_private_page"><![CDATA[{0} page]]></string>
+	<string name="_bx_messenger_lots_private_lot"><![CDATA[Private talk]]></string>
+	<string name="_bx_messenger_lots_class_friends"><![CDATA[All My Friends]]></string>
+	<string name="_bx_messenger_lots_class_my_members"><![CDATA[Shoutbox]]></string>
+	<string name="_bx_messenger_save_part_list"><![CDATA[Save]]></string>
+	<string name="_bx_messenger_save_part_failed"><![CDATA[Participant list can not be empty!]]></string>
+	<string name="_bx_messenger_save_part_success"><![CDATA[Participants list was successfully updated!]]></string>
+	<string name="_bx_messenger_delete_lot"><![CDATA[Are you sure you want to delete "{0}" conversation?]]></string>
+	<string name="_bx_messenger_can_not_delete"><![CDATA[You can not delete this lot!]]></string>	
+	<string name="_bx_messenger_not_participant"><![CDATA[You are not a participant!]]></string>
+	<string name="_bx_messenger_successfully_left"><![CDATA[You left the talk!]]></string>	
+	<string name="_bx_messenger_cant_leave"><![CDATA[You can not leave your own talk!]]></string>
+	<string name="_bx_messenger_leave_chat_confirm"><![CDATA[You you sure you want to leave the talk?]]></string>	
+	<string name="_bx_messenger_menu_new_chat_sys_title"><![CDATA[Messenger button]]></string>	
+	<string name="_bx_messenger_menu_new_chat_action_title"><![CDATA[Message]]></string>	
+	<string name="_bx_messenger_home_page_all_members_block"><![CDATA[Shoutbox]]></string>
+	<string name="_bx_messenger_friends_page_all_friends_block"><![CDATA[My Friends talk]]></string>		
+	<string name="_bx_messenger_notification_request"><![CDATA[Would you like to get notifications about new messages?]]></string>
+	<string name="_bx_messenger_notification_request_yes"><![CDATA[Yes]]></string>	
+	<string name="_bx_messenger_notification_request_no"><![CDATA[No, thanks]]></string>
+	<string name="_bx_messenger_server_url"><![CDATA[Jot Server URL]]></string>
+	<string name="_bx_messenger_online"><![CDATA[Active]]></string>
+	<string name="_bx_messenger_offline"><![CDATA[Offline]]></string>
+	<string name="_bx_messenger_away"><![CDATA[Away]]></string>
+	<string name="_bx_messenger_you_username_title"><![CDATA[You]]></string>
+	<string name="_bx_messenger_remove_jot_confirm"><![CDATA[Are you sure you want to remove this message?]]></string>
+	<string name="_bx_messenger_lots_message_type_public"><![CDATA[General message]]></string>
+	<string name="_bx_messenger_lots_message_type_private"><![CDATA[Private message]]></string>
+	<string name="_bx_messenger_lots_message_type_sets"><![CDATA[Sets message]]></string>
+	<string name="_bx_messenger_lots_message_type_events"><![CDATA[Events message]]></string>
+	<string name="_bx_messenger_lots_message_type_groups"><![CDATA[Groups message]]></string>
+	<string name="_bx_messenger_lots_message_type_broadcast"><![CDATA[Broadcast message]]></string>
+	<string name="_bx_messenger_lot_title_participants_number"><![CDATA[and {0} others]]></string>
+	<string name="_bx_messenger_server_is_not_installed"><![CDATA[Please define Jot Server for messenger]]></string>
+	<string name="_bx_messenger_reconnecting_message"><![CDATA[Connecting]]></string>
+	<string name="_bx_messenger_reconnecting_failed"><![CDATA[Connection failed]]></string>
+	<string name="_bx_messenger_upload_invalid_file_type"><![CDATA[Sorry, this file type forbidden!]]></string>
+	<string name="_bx_messenger_upload_delete"><![CDATA[Delete file]]></string>
+	<string name="_bx_messenger_upload_send_button"><![CDATA[Send]]></string>
+	<string name="_bx_messenger_upload_close"><![CDATA[Close]]></string>
+	<string name="_bx_messenger_delete_confirm"><![CDATA[Are you sure, you want to delete the file?]]></string>
+	<string name="_bx_messenger_post_view_actions"><![CDATA[View actions]]></string>
+	<string name="_bx_messenger_post_attache_files"><![CDATA[Send files]]></string>
+	<string name="_bx_messenger_post_emoji"><![CDATA[Add emoji]]></string>
+	<string name="_bx_messenger_post_file_can_not_be_delete"><![CDATA[You can not remove the file!]]></string>
+	<string name="_bx_messenger_post_confirm_delete_file"><![CDATA[Are you sure you want to delete the file?]]></string>
+	<string name="_bx_messenger_post_file_not_found"><![CDATA[File not found!]]></string>
+	<string name="_bx_messenger_max_files_upload"><![CDATA[Max files number to send in one message]]></string>
+	<string name="_bx_messenger_max_files_upload_error"><![CDATA[Exceeded max files to send in one message (maximum {0})!]]></string>
+	<string name="_bx_messenger_file_is_too_large_error"><![CDATA[File is too large]]></string>
+	<string name="_bx_messenger_file_is_too_large_error_details"><![CDATA[Maximum file size is {filesize}]]></string>
+	<string name="_bx_messenger_invalid_server_response"><![CDATA[Invalid server response ({statusCode})]]></string>
+	<string name="_bx_messenger_attached_files_message"><![CDATA[{0} files received]]></string>
+	<string name="_bx_messenger_lots_menu_settings_title"><![CDATA[Settings]]></string>
+	<string name="_bx_messenger_lots_menu_create_lot_title"><![CDATA[Create new talk]]></string>
+	<string name="_bx_messenger_lots_menu_star_title"><![CDATA[Show only marked/unmarked talks]]></string>
+	<string name="_bx_messenger_search_for_lost_title"><![CDATA[Find talks by title or message]]></string>
+	<string name="_bx_messenger_repost_message"><![CDATA[repost of a message received]]></string>
+	<string name="_bx_messenger_save_button"><![CDATA[Save]]></string>
+	<string name="_bx_messenger_cancel_button"><![CDATA[Cancel]]></string>
+	<string name="_bx_messenger_edit_by"><![CDATA[Edited {0} by {1}]]></string>
+	<string name="_bx_messenger_deleted_by"><![CDATA[Deleted {0} by {1}]]></string>
+	<string name="_bx_messenger_was_deleted"><![CDATA[Message was removed.]]></string>
+	<string name="_bx_messenger_confirm_delete_completely"><![CDATA[Completely delete the message?]]></string>
+	<string name="_bx_messenger_delete_message"><![CDATA[Delete message]]></string>
+	<string name="_bx_messenger_view_message"><![CDATA[View deleted message]]></string>
+	<string name="_bx_messenger_upload_add_comment"><![CDATA[Add Comment...]]></string>
+	<string name="_bx_messenger_start_record_button"><![CDATA[Start]]></string>
+	<string name="_bx_messenger_play_record_button"><![CDATA[Play]]></string>
+	<string name="_bx_messenger_send_record_button"><![CDATA[Send]]></string>
+	<string name="_bx_messenger_send_recorded_video"><![CDATA[Send recorded video]]></string>
+	<string name="_bx_messenger_video_rec"><![CDATA[Rec]]></string>
+	<string name="_bx_messenger_send_message_no_video"><![CDATA[There is no video file to send!]]></string>
+	<string name="_bx_messenger_close_video_confirm"><![CDATA[If you close the window video file will be lost. Are you sure?]]></string>
+	<string name="_bx_messenger_max_video_file_size"><![CDATA[Max length of video-recording in minutes]]></string>
+	<string name="_bx_messenger_max_video_file_exceeds"><![CDATA[Sorry, you have exceeded max video-recording length limit in {0} minutes!]]></string>
+	<string name="_bx_messenger_video_not_supported"><![CDATA[I'm sorry, your browser doesn't support HTML5 video in WebM with VP8/VP9 or MP4 with H.264.]]></string>
+	<string name="_bx_messenger_jump_to_the_bottom"><![CDATA[Jump to the latest message]]></string>
+	<string name="_bx_messenger_mark_all_as_read"><![CDATA[Mark all messages as read]]></string>
+	<string name="_bx_messenger_search_no_results"><![CDATA[Search returned no results]]></string>
+	<string name="_bx_messenger_search_query_issue"><![CDATA[There was an issue with querying the server.]]></string>
+	<string name="_bx_messenger_search_desc"><![CDATA[Joined - {1}<br/>Last login - {0}]]></string>
+	<string name="_bx_messenger_message"><![CDATA[Message]]></string>
+	<string name="_bx_ntfs_alert_action_got_jot_ntfs_personal"><![CDATA[New message in talk where I'm participant]]></string>
+	<string name="_bx_messenger_txt_sample_comment_single"><![CDATA[message - <b>{0}</b>]]></string>
+	<string name="_bx_messenger_txt_sample_comment_file_single"><![CDATA[<b>{0} file(s) </b>]]></string>
+	<string name="_bx_messenger_max_ntfs_number"><![CDATA[Maximum notifications number per member from one Talk during 1 hour]]></string>
+	<string name="_bx_messenger_max_parts_views"><![CDATA[Display viewers icons on messages within Talks that have reached the maximum defined number of participants]]></string>
+	<string name="_bx_messenger_allow_to_remove_messages"><![CDATA[Allow members to moderate their messages (delete/edit)]]></string>
+	<string name="_bx_messenger_remove_messages_immediately"><![CDATA[Remove messages immediately, without administration confirmation]]></string>
+	<string name="_bx_messenger_lots_menu_media"><![CDATA[Files]]></string>
+	<string name="_bx_messenger_no_permissions"><![CDATA[You have no rights to view the content]]></string>
+	<string name="_bx_messenger_use_embedly"><![CDATA[Convert links in message using active Embeds provider]]></string>
+	<string name="_bx_messenger_giphy_api_key"><![CDATA[Giphy Api Key. If you are going to use GIFs animation, you should register and get Api Key here https://developers.giphy.com/dashboard/?create=true]]></string>
+	<string name="_bx_messenger_giphy_type"><![CDATA[Which type to use: GIfs or Stickers (Stickers its are GIFs but with transparent background)]]></string>
+	<string name="_bx_messenger_giphy_content_rating"><![CDATA[Content rating. More details you may find here https://developers.giphy.com/docs/optional-settings#rating]]></string>
+	<string name="_bx_messenger_giphy_limit"><![CDATA[Display defined GIFs number in animation block]]></string>
+	<string name="_bx_messenger_giphy_gifs_button"><![CDATA[Giphy]]></string>
+	<string name="_bx_messenger_giphy_gifs_nothing_found"><![CDATA[Nothing found]]></string>
+	<string name="_bx_messenger_attached_giphy_message"><![CDATA[Received animation]]></string>
+	<string name="_bx_messenger_max_drop_down_select"><![CDATA[Maximum users number in dropdown search result]]></string>
+	<string name="_bx_messenger_reaction_title_author"><![CDATA[You]]></string>
+	<string name="_bx_messenger_reaction_title"><![CDATA[{0} reacted with: {1}]]></string>
+	<string name="_bx_messenger_uploading_file"><![CDATA[Uploading]]></string>
+	<string name="_bx_messenger_upload_is_complete"><![CDATA[Upload is complete]]></string>
+	<string name="_bx_messenger_upload_cancelled"><![CDATA[Upload cancelled]]></string>
+	<string name="_bx_messenger_uploading_remove_button"><![CDATA[Remove]]></string>
+	<string name="_bx_messenger_wait_for_uploading"><![CDATA[Please wait till the all files are uploaded!]]></string>
+	<string name="_bx_messenger_emoji_text_title"><![CDATA[Choose Emoji]]></string>
+	<string name="_bx_messenger_emoji_text_reaction"><![CDATA[Choose Reaction]]></string>
+	<string name="_bx_messenger_emoji_search"><![CDATA[Search]]></string>
+	<string name="_bx_messenger_emoji_clear"><![CDATA[Clear]]></string>
+	<string name="_bx_messenger_emoji_not_found"><![CDATA[No Emoji Found]]></string>
+	<string name="_bx_messenger_emoji_skintext"><![CDATA[Choose your default skin tone]]></string>
+	<string name="_bx_messenger_emoji_categ_search"><![CDATA[Search Results]]></string>
+	<string name="_bx_messenger_emoji_categ_recent"><![CDATA[Frequently Used]]></string>
+	<string name="_bx_messenger_emoji_categ_people"><![CDATA[Smileys & People]]></string>
+	<string name="_bx_messenger_emoji_categ_nature"><![CDATA[Animals & Nature]]></string>
+	<string name="_bx_messenger_emoji_categ_foods"><![CDATA[Food & Drink]]></string>
+	<string name="_bx_messenger_emoji_categ_activity"><![CDATA[Activity]]></string>
+	<string name="_bx_messenger_emoji_categ_places"><![CDATA[Travel & Places]]></string>
+	<string name="_bx_messenger_emoji_categ_objects"><![CDATA[Objects]]></string>
+	<string name="_bx_messenger_emoji_categ_symbols"><![CDATA[Symbols]]></string>
+	<string name="_bx_messenger_emoji_categ_flags"><![CDATA[Flags]]></string>
+	<string name="_bx_messenger_emoji_categ_custom"><![CDATA[Custom]]></string>
+	<string name="_bx_messenger_emoji_categorieslabel"><![CDATA[Emoji categories]]></string>
+	<string name="_bx_messenger_emoji_skintones_1"><![CDATA[Default Skin Tone]]></string>
+	<string name="_bx_messenger_emoji_skintones_2"><![CDATA[Light Skin Tone]]></string>
+	<string name="_bx_messenger_emoji_skintones_3"><![CDATA[Medium-Light Skin Tone]]></string>
+	<string name="_bx_messenger_emoji_skintones_4"><![CDATA[Medium Skin Tone]]></string>
+	<string name="_bx_messenger_emoji_skintones_5"><![CDATA[Medium-Dark Skin Tone]]></string>
+	<string name="_bx_messenger_emoji_skintones_6"><![CDATA[Dark Skin Tone]]></string>
+	<string name="_bx_messenger_emoji_set"><![CDATA[Choose an emoji style to use for reactions and in-text messages]]></string>
+	<string name="_bx_messenger_reactions_size"><![CDATA[Size of the emoji in reactions in pixels]]></string>
+	<string name="_bx_messenger_show_emoji_preview"><![CDATA[Show emojis preview in Emoji popup block]]></string>
+	<string name="_bx_messenger_file_download"><![CDATA[Download file]]></string>
+	<string name="_bx_messenger_txt_subobject_added_single"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> sent {subentry_sample}]]></string>
+	<string name="_bx_messenger_txt_sample_comment_giphy_single"><![CDATA[an animation]]></string>
+	<string name="_bx_messenger_lots_menu_video_conf_start"><![CDATA[Start video conference]]></string>
+	<string name="_bx_messenger_lots_menu_video_conf_join"><![CDATA[Join to video conference]]></string>
+	<string name="_bx_messenger_jitsi_err_can_join_conference"><![CDATA[Sorry, you can not participate in video conference]]></string>
+	<string name="_bx_messenger_jitsi_err_no_members"><![CDATA[There are no members in the talk to start conversation]]></string>
+	<string name="_bx_messenger_jitsi_meet_app_title"><![CDATA[{0} video conference]]></string>
+	<string name="_bx_messenger_jitsi_meet_me"><![CDATA[me]]></string>
+	<string name="_bx_messenger_jitsi_err_can_not_be_created"><![CDATA[Video Conference can not be create.]]></string>
+	<string name="_bx_messenger_jitsi_err_vc_was_not_found"><![CDATA[Video conference was not found.]]></string>
+	<string name="_bx_messenger_jitsi_has_started"><![CDATA[Video conference has been started <b>{0}</b>]]></string>
+	<string name="_bx_messenger_jitsi_conference"><![CDATA[<b>Video conference</b>, duration - <b>{0}</b>]]></string>
+	<string name="_bx_messenger_jitsi_vc_duration"><![CDATA[{0} min, {0} s.]]></string>
+	<string name="_bx_messenger_jitsi_vc_duration_h"><![CDATA[{0} h, {1} min, {2} s.]]></string>
+	<string name="_bx_messenger_jitsi_vc_duration_m"><![CDATA[{0} min, {1} s.]]></string>
+	<string name="_bx_messenger_jitsi_vc_duration_s"><![CDATA[{0} seconds.]]></string>
+	<string name="_bx_messenger_jitsi_vc_into_title"><![CDATA[Message was sent in video conference created by {0} at {1}]]></string>
+	<string name="_bx_messenger_jitsi_vc_join"><![CDATA[Join]]></string>
+	<string name="_bx_messenger_are_you_sure_close_jisti"><![CDATA[Are you sure you want to leave the conference?]]></string>
+	<string name="_bx_messenger_allow_to_use_jitsi"><![CDATA[Allow to use Jitsi]]></string>
+	<string name="_bx_messenger_jisti_connection_error"><![CDATA[Can not connect to video conference server. Please check your internet connection or try again later.]]></string>
+	<string name="_bx_messenger_jisti_not_active_conference"><![CDATA[Active conference is not found. Please reload the talk or try to create your own conference.]]></string>
+	<string name="_bx_messenger_jitsi_server"><![CDATA[Use defined domain as Jitsi server]]></string>
+	<string name="_bx_messenger_jitsi_chat_enable"><![CDATA[Enable Jitsi chat inside of Jitsi conference]]></string>
+	<string name="_bx_messenger_jitsi_chat_sync"><![CDATA[Synchronize Jitsi chat with Messenger history (in this case all messages from Jitsi chat will be duplicated in Messenger history also)]]></string>
+	<string name="_bx_messenger_jitsi_hide_info"><![CDATA[Show chat info (members will be able to see chat room link and share it with non-members)]]></string>
+	<string name="_bx_messenger_jitsi_enable_only_for_private"><![CDATA[Enable video conference only for private talks]]></string>
+	<string name="_bx_messenger_jitsi_support_url"><![CDATA[If indicated some of the errors, dialogs may point to this support URL for help]]></string>
+	<string name="_bx_messenger_jitsi_watermark"><![CDATA[Enable watermark (<b>Note</b>: If you are using default Jitsi server, you will see original Jitsi watermark). You may read more <a href="https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md">here</a> about how to install your own deployment.]]></string>
+	<string name="_bx_messenger_jitsi_watermark_link"><![CDATA[Jitsi Watermark link (user will be redirected to this link when clicks on watermark)]]></string>
+	<string name="_bx_messenger_jitsi_remote_user"><![CDATA[Participant]]></string>
+	<string name="_bx_messenger_lot_parts_error"><![CDATA[Talk with the same participants list already exists.]]></string>
+	<string name="_bx_messenger_jitsi_err_cant_type_use"><![CDATA[Sorry, video conference for is not allowed for you.]]></string>
+	<string name="_bx_messenger_file_type_is_not_allowed"><![CDATA[This file type is not allowed!]]></string>
+	<string name="_bx_messenger_jitsi_mobile_warning"><![CDATA[To make video conference more stable and reliable we have to use mobile application. It is possible to connect via our own mobile app (just download it from the App Store) or through the original Jitsi application. Continue with jitsi app?]]></string>
+	<string name="_bx_messenger_send_message_not_allowed"><![CDATA[Send messages is not allowed for your account!]]></string>
+	<string name="_bx_messenger_menu_video_conference_sys_title"><![CDATA[Video Chat]]></string>
+	<string name="_bx_messenger_menu_video_conference_action_title"><![CDATA[Video Chat]]></string>
+	<string name="_bx_messenger_acl_action_video_conference"><![CDATA[Create personal video chat]]></string>
+	<string name="_bx_messenger_profile_not_found"><![CDATA[Profile not found!]]></string>
+	<string name="_bx_messenger_push_vc_bx_organizations_message_title"><![CDATA[Video Meeting]]></string>
+	<string name="_bx_messenger_public_vc_bx_organizations_message"><![CDATA[{0} meeting has been started]]></string>
+	<string name="_bx_messenger_push_vc_bx_persons_message_title"><![CDATA[Personal Video call]]></string>
+	<string name="_bx_messenger_public_vc_bx_persons_message"><![CDATA[Video call from {0}]]></string>
+	<string name="_bx_messenger_use_mentions"><![CDATA[Enable mentions in messages]]></string>
+	<string name="_bx_messenger_loading"><![CDATA[loading...]]></string>
+	<string name="_bx_messenger_notification_subject_message"><![CDATA[Message from {0}]]></string>
+	<string name="_bx_messenger_notification_subject_mention"><![CDATA[{0} mentioned You]]></string>
+	<string name="_bx_ntfs_alert_action_got_mention_ntfs_personal"><![CDATA[Mentions in Messenger]]></string>
+	<string 
+name="_bx_ntfs_alert_action_got_broadcast_ntfs_personal"><![CDATA[Broadcast message from Messenger]]></string>
+	<string name="_bx_messenger_txt_subobject_added_message"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> sent a {subentry_sample}]]></string>
+	<string name="_bx_messenger_txt_subobject_added_mention"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> mentioned you in a {subentry_sample}]]></string>
+	<string name="_bx_messenger_date_time_today"><![CDATA[Today]]></string>
+	<string name="_bx_messenger_acl_action_create_talks"><![CDATA[Create talks]]></string>
+	<string name="_bx_messenger_acl_action_administrate_talks"><![CDATA[Administrate talks (remove/edit)]]></string>
+	<string name="_bx_messenger_acl_action_send_messages"><![CDATA[Send messages]]></string>
+	<string name="_bx_messenger_acl_action_create_vc"><![CDATA[Create video conferences]]></string>
+	<string name="_bx_messenger_disable_contact_privacy"><![CDATA[Disable profiles contact privacy settings. <b>Note</b>: if you enable this option any profile be able to contact with any!]]></string>
+	<string name="_bx_messenger_allow_to_moderate_messages"><![CDATA[Allow talks' authors moderate messages (delete/edit)]]></string>
+	<string name="_bx_messenger_clear_lot"><![CDATA[Are you sure you want to remove the all messages from "{0}" convo?]]></string>
+	<string name="_bx_messenger_acl_action_video_recorder"><![CDATA[Use video recording]]></string>
+	<string name="_bx_messenger_jwt_app_id"><![CDATA[JWT token App id]]></string>
+	<string name="_bx_messenger_jwt_app_secret"><![CDATA[JWT token App secret]]></string>
+	<string name="_bx_messenger_lot_action_not_allowed"><![CDATA[{0} action is not allowed for this talk]]></string>
+	<string name="_bx_messenger_lot_can_change_settings"><![CDATA[You can not change settings of the talk]]></string>
+	<string name="_bx_messenger_lot_options_item_msg"><![CDATA[messages]]></string>
+	<string name="_bx_messenger_lot_options_item_giphy"><![CDATA[animation]]></string>
+	<string name="_bx_messenger_lot_options_item_files"><![CDATA[files]]></string>
+	<string name="_bx_messenger_lot_options_item_video_rec"><![CDATA[video recordings]]></string>
+	<string name="_bx_messenger_lot_options_item_smiles"><![CDATA[smiles]]></string>
+	<string name="_bx_messenger_lot_options_title"><![CDATA[Allow to perform selected actions for the talk:]]></string>
+	<string name="_bx_messenger_max_max_lots_number"><![CDATA[Default number of talks in Talks' list block]]></string>
+	<string name="_bx_messenger_enable_joined_organizations"><![CDATA[Allow organization to communicate with profile only if user joined or followed the organization]]></string>
+	<string name="_bx_messenger_mark_as_unread"><![CDATA[Mark as unread]]></string>
+	<string name="_bx_messenger_menu_item_title_video_call"><![CDATA[Video Call]]></string>
+	<string name="_bx_messenger_menu_item_title_star"><![CDATA[Mark talks as important]]></string>
+	<string name="_bx_messenger_menu_item_title_mute"><![CDATA[Mute talk]]></string>
+	<string name="_bx_messenger_menu_set_title_talk_menu"><![CDATA[Talk menu]]></string>
+	<string name="_bx_messenger_acl_action_join_vc"><![CDATA[Join to video conference]]></string>
+	<string name="_bx_messenger_acl_action_join_personal_vc"><![CDATA[Join to personal video conference]]></string>
+	<string name="_bx_messenger_video_recorder_is_not_available"><![CDATA[Video Recorder is not available in your browser]]></string>
+	<string name="_bx_messenger_video_recorder_is_blocked"><![CDATA[In order to use this function, please allow {0} access to your camera. Thanks and enjoy!]]></string>
+	<string name="_bx_messenger_acl_action_send_files"><![CDATA[Send files]]></string>
+	<string name="_bx_messenger_show_friends"><![CDATA[Show member's friends list in talks' area if there are no talks yet]]></string>
+	<string name="_bx_messenger_contact_privacy_not_allowed"><![CDATA[Participant of the talk doesn't allow to contact]]></string>
+	<string name="_bx_messenger_empty_history"><![CDATA[Empty]]></string>
+	<string name="_bx_messenger_left_menu_groups_item_create_group"><![CDATA[Create group]]></string>
+	<string name="_bx_messenger_empty_users_list"><![CDATA[No profiles found]]></string>
+	<string name="_bx_messenger_add_users_message"><![CDATA[Add users to start messaging]]></string>
+	<string name="_bx_messenger_users_creation_new_message"><![CDATA[New message...]]></string>
+	<string name="_bx_messenger_groups_name"><![CDATA[Name]]></string>
+	<string name="_bx_messenger_groups_name_info"><![CDATA[Group's title available for members to see]]></string>
+	<string name="_bx_messenger_groups_desc"><![CDATA[Description]]></string>
+	<string name="_bx_messenger_groups_desc_info"><![CDATA[Describe the group, it helps users to find appropriate talk]]></string>
+	<string name="_bx_messenger_groups_privacy"><![CDATA[Privacy]]></string>
+	<string name="_bx_messenger_groups_privacy_info"><![CDATA[Allow to view group only for selected privacy]]></string>
+	<string name="_bx_messenger_groups_create_form_title"><![CDATA[Create Group]]></string>
+	<string name="_bx_messenger_groups_edit_form_title"><![CDATA[Edit Group]]></string>
+	<string name="_bx_messenger_form_entry_input_allow_view_groups_to"><![CDATA[Allow view groups to]]></string>
+	<string name="_bx_messenger_nothing_has_been_saved"><![CDATA[Nothing has been saved!]]></string>
+	<string name="_bx_messenger_threads_title"><![CDATA[Threads]]></string>
+	<string name="_bx_messenger_nothing_found"><![CDATA[Nothing found]]></string>
+	<string name="_bx_messenger_jot_replies"><![CDATA[replies]]></string>
+	<string name="_bx_messenger_view_replies"><![CDATA[View replies]]></string>
+	<string name="_bx_messenger_menu_item_title_group_list"><![CDATA[Talks list]]></string>
+	<string name="_bx_messenger_groups_talks_title"><![CDATA[Talks list]]></string>
+	<string name="_bx_messenger_list_area_inbox"><![CDATA[Inbox]]></string>
+	<string name="_bx_messenger_text_area_topic_title"><![CDATA[Title]]></string>
+	<string name="_bx_messenger_left_menu_item_direct_messages"><![CDATA[Direct Messages]]></string>
+	<string name="_bx_messenger_left_menu_item_threads"><![CDATA[Threads]]></string>
+	<string name="_bx_messenger_left_menu_item_pages"><![CDATA[Pages]]></string>
+	<string name="_bx_messenger_check_toxic"><![CDATA[Block messages with toxic content. This option can not work without Antispam module.]]></string>
+	<string name="_bx_messenger_toxic_message"><![CDATA[This message can not be PUBLISHED because doesn't meet our community standards. Please use kinder, gentler words if you want to try again.]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_inbox"><![CDATA[Inbox]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_threads"><![CDATA[Threads]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_direct"><![CDATA[Direct]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_files"><![CDATA[Files]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_replies"><![CDATA[Replies]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_saved"><![CDATA[Saved messages]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_favorites"><![CDATA[Favorite chats]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_mr"><![CDATA[Mentions & Reactions]]></string>
+	<string name="_bx_messenger_menu_item_title_parent_lot"><![CDATA[Open original message]]></string>
+	<string name="_bx_messenger_jot_server_jwt"><![CDATA[Secret word for JWT Token Authentication with Jot Server. Set the same <b>secret</b> word in the config/config.json file of the Jot Server to enable this option. This allows connections to Jot Server only from authorized sites. Leave it empty if you don't want to use JWT tokens with Jot Server.]]></string>
+	<string name="_bx_messenger_nav_menu_title_view"><![CDATA[Main menu]]></string>
+	<string name="_bx_messenger_groups_menu_title_view"><![CDATA[Groups Menu]]></string>
+	<string name="_bx_messenger_menu_title_view"><![CDATA[Talk's header menu]]></string>
+	<string name="_bx_messenger_page_main_messenger_block"><![CDATA[Main Messenger page]]></string>
+	<string name="_bx_messenger_pages"><![CDATA[Pages]]></string>
+	<string name="_bx_messenger_menu_lot_info_title"><![CDATA[Talk's info menu]]></string>
+	<string name="_bx_messenger_lots_menu_clear"><![CDATA[Clear history]]></string>
+	<string name="_bx_messenger_lots_menu_settings"><![CDATA[Settings]]></string>
+	<string name="_bx_messenger_menu_set_title_talk_info_menu"><![CDATA[Talk's info menu]]></string>
+	<string name="_bx_messenger_menu_set_title_jot_menu"><![CDATA[Message's menu]]></string>
+	<string name="_bx_messenger_jot_menu_reaction"><![CDATA[Add reaction]]></string>
+	<string name="_bx_messenger_jot_menu_reply"><![CDATA[Reply]]></string>
+	<string name="_bx_messenger_reply_audio_file"><![CDATA[Audio file - "{0}"]]></string>
+	<string name="_bx_messenger_jot_menu_edit"><![CDATA[Edit]]></string>
+	<string name="_bx_messenger_jot_menu_remove"><![CDATA[Delete]]></string>
+	<string name="_bx_messenger_jot_menu_thread"><![CDATA[Reply in thread]]></string>
+	<string name="_bx_messenger_jot_menu_save"><![CDATA[Bookmark]]></string>
+	<string name="_bx_messenger_jot_menu_share"><![CDATA[Copy link]]></string>
+	<string name="_bx_messenger_bad_request"><![CDATA[Bad request attempt]]></string>
+	<string name="_bx_messenger_search_criteria_list"><![CDATA[Use these criteria for messages filtration]]></string>
+	<string name="_bx_messenger_search_item_titles"><![CDATA[talks titles]]></string>
+	<string name="_bx_messenger_search_item_participants"><![CDATA[participants names]]></string>
+	<string name="_bx_messenger_search_item_content"><![CDATA[messages content]]></string>
+	<string name="_bx_messenger_time_in_history"><![CDATA[Use <b>from now</b> time format in Talk's history]]></string>
+	<string name="_bx_messenger_dont_show_search_desc"><![CDATA[Don't show description in search profiles popup]]></string>
+	<string name="_bx_messenger_use_unique_mode"><![CDATA[Switch to Two-Column View (Simple Mode)]]></string>
+	<string name="_bx_messenger_talk_is_not_allowed"><![CDATA[Talk is not available for you!]]></string>
+	<string name="_bx_messenger_connect_friends_only"><![CDATA[Allow to create Talks only with friends]]></string>
+	<string name="_bx_messenger_talk_types_market_title"><![CDATA[Conversation with {opponent}]]></string>
+	<string name="_bx_messenger_view_in_chat"><![CDATA[View in talk]]></string>
+	<string name="_bx_messenger_are_you_sure_leave"><![CDATA[Are you sure you want to leave "{0}" ?]]></string>
+	<string name="_bx_messenger_page_unknown"><![CDATA[Unknown]]></string>
+	<string name="_bx_messenger_txt_sample_comment_reply_single"><![CDATA[reply]]></string>
+	<string name="_bx_messenger_txt_sample_comment_repost_single"><![CDATA[repost]]></string>
+	<string name="_bx_messenger_txt_sample_email_push"><![CDATA[sent {0}]]></string>
+	<string name="_bx_messenger_lot_parts_empty"><![CDATA[Please select at least one user to communicate with!]]></string>
+	<string name="_bx_messenger_dont_update_title"><![CDATA[Don't update page title when new messages income]]></string>
+	<string name="_bx_messenger_acl_action_edit_messages"><![CDATA[Edit messages]]></string>
+	<string name="_bx_messenger_acl_action_delete_messages"><![CDATA[Delete messages]]></string>
+	<string name="_bx_messenger_files_can_not_be_uploaded"><![CDATA[Files can not be uploaded to the conversation!]]></string>
+	<string name="_bx_messenger_not_allowed_to_read"><![CDATA[You can not read this message, because you are not a participant of the talk!]]></string>
+	<string name="_bx_messenger_not_found_to_read"><![CDATA[Talks with the message was not found, please specify the url!]]></string>
+	<string name="_bx_messenger_menu_jot_title"><![CDATA[Message's menu]]></string>
+	<string name="_bx_messenger_share_jot"><![CDATA[Copy link]]></string>
+	<string name="_bx_messenger_neo_app_form"><![CDATA[Messenger text area from]]></string>
+	<string name="_bx_messenger_neo_app_form"><![CDATA[Send Message From]]></string>
+	<string name="_bx_messenger_neo_app_form_display_send_message"><![CDATA[Send Message]]></string>
+	<string name="_bx_messenger_neo_app_form_input_submit"><![CDATA[Submit]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_submit"><![CDATA[Send]]></string>
+	<string name="_bx_messenger_neo_app_form_input_cancel"><![CDATA[Cancel]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_cancel"><![CDATA[Cancel]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_files"><![CDATA[File]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_message"><![CDATA[Message]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_parent_id"><![CDATA[Parent ID]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_message_id"><![CDATA[Message ID]]></string>
+	<string name="_bx_messenger_page_block_contacts_title"><![CDATA[Contacts]]></string>
+	<string name="_bx_messenger_profile_title_snippet_meta"><![CDATA[Snippet Meta Info]]></string>
+	<string name="_bx_messenger_set_title_profile_snippet_meta"><![CDATA[Snippet Profile Meta for Contacts block]]></string>
+	<string name="_bx_messenger_item_title_sm_message"><![CDATA[Message]]></string>
+	<string name="_bx_messenger_broadcast_fields"><![CDATA[Broadcast filter fields]]></string>
+	<string name="_bx_messenger_broadcast_field_membership"><![CDATA[Membership level]]></string>
+	<string name="_bx_messenger_broadcast_profile_fields_title"><![CDATA[Select criteria to broadcast]]></string>
+	<string name="_bx_messenger_broadcast_field_calculate"><![CDATA[View profiles number involved to broadcast by criteria]]></string>
+	<string name="_bx_messenger_broadcast_total_profiles"><![CDATA[Total profiles to broadcast {0}]]></string>
+	<string name="_bx_messenger_lots_menu_info"><![CDATA[Info]]></string>
+
+	<string name="_bx_messenger_create_convo_menu_standard"><![CDATA[Profiles]]></string>
+	<string name="_bx_messenger_create_convo_menu_friends"><![CDATA[Friends]]></string>
+	<string name="_bx_messenger_create_convo_menu_followers"><![CDATA[Followers]]></string>
+	<string name="_bx_messenger_create_convo_menu_followers_system"><![CDATA[Followers]]></string>
+	<string name="_bx_messenger_create_convo_menu_broadcast"><![CDATA[Broadcast]]></string>
+	<string name="_bx_messenger_create_convo_menu_standard_system"><![CDATA[Group]]></string>
+	<string name="_bx_messenger_create_convo_menu_friends_system"><![CDATA[Friends]]></string>
+	<string name="_bx_messenger_create_convo_menu_broadcast_system"><![CDATA[Broadcast]]></string>
+	<string name="_bx_messenger_create_convo_menu_title"><![CDATA[Create Convos menu]]></string>
+	<string name="_bx_messenger_create_convo_menu_set_title"><![CDATA[Create Convos Menu set]]></string>
+	<string name="_bx_messenger_create_convo_template_title"><![CDATA[Create Convos menu template]]></string>
+
+	<string name="_bx_messenger_filter_criteria_membership"><![CDATA[Membership level]]></string>
+
+	<string name="_bx_messenger_broadcast_field_email"><![CDATA[Email]]></string>
+	<string name="_bx_messenger_broadcast_field_push"><![CDATA[Push]]></string>
+	<string name="_bx_messenger_broadcast_field_site"><![CDATA[Site]]></string>
+	<string name="_bx_messenger_broadcast_field_notify"><![CDATA[Notify user]]></string>
+	<string name="_bx_messenger_broadcast_field_notify_info">
+<![CDATA[Participants will be notified using selected methods]]></string>
+	<string name="_bx_messenger_broadcast_field_author"><![CDATA[Author of the broadcast]]></string>
+	<string name="_bx_messenger_broadcast_field_author_info"><![CDATA[If to leave it empty, creator will be used as author]]></string>
+	<string name="_bx_messenger_broadcast_message_title"><![CDATA[Broadcast message]]></string>
+	<string name="_bx_messenger_broadcast_message_body"><![CDATA[Broadcast]]></string>
+	<string name="_bx_messenger_txt_subobject_added_broadcast"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> created broadcast {subentry_sample}]]></string>
+	<string name="_bx_messenger_notification_subject_broadcast"><![CDATA[Broadcast from {0}]]></string>
+
+	<string name="_bx_messenger_filter_criteria_additional_message"><![CDATA[Additional profiles involved to the broadcast]]></string>
+	<string name="_bx_messenger_filter_criteria_friends_message"><![CDATA[You have totally {0} friends to participate in this broadcast]]></string>
+	<string name="_bx_messenger_filter_criteria_followers_message"><![CDATA[You have totally {0} followers to participate in this broadcast]]></string>
+	<string name="_bx_messenger_filter_criteria_on_friends"><![CDATA[You have no friends to create broadcast]]></string>
+	<string name="_bx_messenger_filter_criteria_on_followers"><![CDATA[You have no followers to create broadcast]]></string>
+	<string name="_bx_messenger_acl_action_create_broadcast"><![CDATA[Create broadcasts]]></string>
+
+	<string name="_bx_messenger_convo_info_participants"><![CDATA[Participants]]></string>
+	<string name="_bx_messenger_convo_info_messages"><![CDATA[Messages]]></string>
+	<string name="_bx_messenger_convo_info_files"><![CDATA[Files]]></string>
+	<string name="_bx_messenger_filter_criteria_form"><![CDATA[Broadcast from with criteria]]></string>
+	<string name="_bx_messenger_filter_criteria_form_display"><![CDATA[Broadcast from with criteria]]></string>
+	<string name="_bx_messenger_no_criteria_available"><![CDATA[Sorry, no criteria for filtration found!]]></string>
+	<string name="_bx_messenger_create_talk_confirm"><![CDATA[You are sure, you would like to create the talk?]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_id"><![CDATA[Convo Id]]></string>
+	<string name="_bx_messenger_neo_app_form_input_id"><![CDATA[Convo Id]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_reply"><![CDATA[Parent message id]]></string>
+	<string name="_bx_messenger_no_server"><![CDATA[Messenger settings do not have the Jot Server Url defined, which means that real-time communication is not currently possible]]></string>
+	<string name="_bx_messenger_jot_menu_remove_save"><![CDATA[Remove bookmark]]></string>
+
+	<string name="_bx_messenger_cpt_category_settings"><![CDATA[Settings]]></string>
+	<string name="_bx_messenger_quill_toolbar_settings"><![CDATA[Quill Editor Toolbar Settings (JSON array)]]></string>
+	<string name="_bx_messenger_cpt_category_security"><![CDATA[Security]]></string>
+	<string name="_bx_messenger_cpt_category_moderation"><![CDATA[Moderation]]></string>
+	<string name="_bx_messenger_cpt_category_giphy"><![CDATA[Giphy]]></string>
+	<string name="_bx_messenger_cpt_category_emoji"><![CDATA[Emoji]]></string>
+	<string name="_bx_messenger_cpt_category_jitsi"><![CDATA[Jitsi]]></string>
+	<string name="_bx_messenger_cpt_category_privacy"><![CDATA[Privacy]]></string>
+	<string name="_bx_messenger_cpt_category_view"><![CDATA[View]]></string>
+	<string name="_bx_messenger_cpt_category_search"><![CDATA[Search]]></string>
+
+	<string name="_bx_messenger_hide_parts"><![CDATA[Hide last answered users in talks briefs]]></string>
+	<string name="_bx_messenger_show_search_box"><![CDATA[Enable search input in the talks list column is open by default]]></string>
+
+	<string name="_bx_messenger_giphy_powered_by"><![CDATA[Enabling 'Powered By' logo: required step for Giphy integration in Production]]></string>
+	<string name="_bx_messenger_video_record_is_not_supported"><![CDATA[Video recording is not supported: {0}]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_groups"><![CDATA[Groups]]></string>
+
+	<string name=""><![CDATA[]]></string>
+</resources>

+ 427 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/langs/ru.xml

@@ -0,0 +1,427 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="ru" flag="ru" title="Russian">
+	<string name="_bx_messenger"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_page_title_sys_view"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_page_title_view"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_page_block_title_post"><![CDATA[Отправить сообщение]]></string>
+	<string name="_bx_messenger_page_block_title_view"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_page_block_title_messenger"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_txt_msg_no_results"><![CDATA[Пусто]]></string>
+	<string name="_bx_messenger_menu_item_title_messenger"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_page_title_sys_home"><![CDATA[Страница болталки]]></string>
+	<string name="_bx_messenger_page_title_home"><![CDATA[Мои сообщения]]></string>
+	<string name="_bx_messenger_page_title_sys_main"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_page_title_main"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_page_inbox_block_title"><![CDATA[Мои разговоры]]></string>
+	<string name="_bx_messenger_page_conversation_block_title"><![CDATA[Сообщения]]></string>
+	<string name="_bx_messenger_page_block_title"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_send_message_only_for_logged"><![CDATA[Извините, только зарегистрированные участники могут отправлять сообщения!]]></string>
+	<string name="_bx_messenger_symbols_num_option"><![CDATA[Максимальный длинна сообщения(количество символов)]]></string>
+	<string name="_bx_messenger_send_message_save_error"><![CDATA[Сообщение не было отправлено!]]></string>
+	<string name="_bx_messenger_send_message_no_data"><![CDATA[Пустое сообщение!]]></string>
+	<string name="_bx_messenger_lots_type_all"><![CDATA[Все]]></string>
+	<string name="_bx_messenger_symbols_num_brief_jot"><![CDATA[Количество символов сообщения в списке разговоров]]></string>
+	<string name="_bx_messenger_post_area_message"><![CDATA[Cообщение ...]]></string>
+	<string name="_bx_messenger_title_search"><![CDATA[Найти..]]></string>
+	<string name="_bx_messenger_not_logged"><![CDATA[Вам необходимо зарегистрироваться!]]></string>
+	<string name="_bx_messenger_not_found"><![CDATA[Разговор не найден!]]></string>
+	<string name="_bx_messenger_jot_number_default"><![CDATA[Количество сообщений в списке сообщений]]></string>
+	<string name="_bx_messenger_what_do_think"><![CDATA[Что вы об этом думаете?]]></string>
+	<string name="_bx_messenger_post_button_title"><![CDATA[Оправить]]></string>
+	<string name="_bx_messenger_menu_item_title_system_messenger"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_menu_notifications_item_sys_title"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_menu_notifications_item_title"><![CDATA[Болталка]]></string>
+	<string name="_bx_messenger_name_place_holder"><![CDATA[Напишите имя тут...]]></string>
+	<string name="_bx_messenger_typing_message"><![CDATA[пишет сообщение...]]></string>
+	<string name="_bx_messenger_linked_title"><![CDATA[Страница: {0}]]></string>
+	<string name="_bx_messenger_lots_type_public"><![CDATA[Общие]]></string>
+	<string name="_bx_messenger_lots_type_private"><![CDATA[Личные]]></string>
+	<string name="_bx_messenger_lots_type_sets"><![CDATA[Разное]]></string>
+	<string name="_bx_messenger_lots_type_events"><![CDATA[События]]></string>
+	<string name="_bx_messenger_lots_type_groups"><![CDATA[Группы]]></string>
+	<string name="_bx_messenger_lots_menu_add_part"><![CDATA[Редактировать список участников]]></string>
+	<string name="_bx_messenger_lots_menu_leave"><![CDATA[Покинуть]]></string>
+	<string name="_bx_messenger_lots_menu_delete"><![CDATA[Удалить]]></string>
+	<string name="_bx_messenger_lots_menu_mute"><![CDATA[Выключить оповещения]]></string>
+	<string name="_bx_messenger_lots_menu_unmute"><![CDATA[Включить сообщения]]></string>
+	<string name="_bx_messenger_max_jot_number_in_history"><![CDATA[Количество записей, подгружаемых в историю разговора]]></string>
+	<string name="_bx_messenger_lots_menu_mute_info_off"><![CDATA[При выключении оповещений, вы перестанете получать пуш нотификацию]]></string>
+	<string name="_bx_messenger_lots_menu_mute_info_on"><![CDATA[Включить уведомления о новых сообщениях]]></string>
+	<string name="_bx_messenger_lots_menu_star_on"><![CDATA[Пометить разговор как важный]]></string>
+	<string name="_bx_messenger_lots_menu_star_off"><![CDATA[Отменить метку]]></string>
+	<string name="_bx_messenger_lots_private_lot_info"><![CDATA[Открыт для всех у кого есть доступ к {0}]]></string>
+	<string name="_bx_messenger_lots_private_page"><![CDATA[{0} страницы]]></string>
+	<string name="_bx_messenger_lots_private_lot"><![CDATA[Приватный разговор]]></string>
+	<string name="_bx_messenger_lots_class_friends"><![CDATA[Мои друзья]]></string>
+	<string name="_bx_messenger_lots_class_my_members"><![CDATA[Кричалка]]></string>
+	<string name="_bx_messenger_save_part_list"><![CDATA[Сохранить]]></string>
+	<string name="_bx_messenger_save_part_failed"><![CDATA[Список участников не может быть пуст!]]></string>
+	<string name="_bx_messenger_save_part_success"><![CDATA[Список участников был успешно обновлён!]]></string>
+	<string name="_bx_messenger_delete_lot"><![CDATA[Вы уверены, что хотите удалить "{0}" разговор?]]></string>
+	<string name="_bx_messenger_can_not_delete"><![CDATA[Вы не можете удалить этот разговор!]]></string>
+	<string name="_bx_messenger_not_participant"><![CDATA[Вы не являетесь участником разговора!]]></string>
+	<string name="_bx_messenger_successfully_left"><![CDATA[Вы покинули разговор!]]></string>
+	<string name="_bx_messenger_cant_leave"><![CDATA[Вы не можете покинуть свой собственный разговор!]]></string>
+	<string name="_bx_messenger_leave_chat_confirm"><![CDATA[Вы уверены, что хотите покинуть разговор?]]></string>
+	<string name="_bx_messenger_menu_new_chat_sys_title"><![CDATA[Кнопка болталки]]></string>
+	<string name="_bx_messenger_menu_new_chat_action_title"><![CDATA[Сообщение]]></string>
+	<string name="_bx_messenger_home_page_all_members_block"><![CDATA[Кричалка]]></string>
+	<string name="_bx_messenger_friends_page_all_friends_block"><![CDATA[Разговор друзей]]></string>
+	<string name="_bx_messenger_notification_request"><![CDATA[Вы не хотели бы получать оповещение о новых сообщениях?]]></string>
+	<string name="_bx_messenger_notification_request_yes"><![CDATA[Да]]></string>
+	<string name="_bx_messenger_notification_request_no"><![CDATA[Нет, спасибо]]></string>
+	<string name="_bx_messenger_server_url"><![CDATA[Ссылка на сервер для работы болталки]]></string>
+	<string name="_bx_messenger_online"><![CDATA[Активен]]></string>
+	<string name="_bx_messenger_offline"><![CDATA[Нет на сайте]]></string>
+	<string name="_bx_messenger_away"><![CDATA[Отошёл]]></string>
+	<string name="_bx_messenger_you_username_title"><![CDATA[Вы]]></string>
+	<string name="_bx_messenger_remove_jot_confirm"><![CDATA[Вы уверены, что хотите удалить эту запись?]]></string>
+	<string name="_bx_messenger_lots_message_type_public"><![CDATA[Публичное сообщение]]></string>
+	<string name="_bx_messenger_lots_message_type_private"><![CDATA[Личное сообщение]]></string>
+	<string name="_bx_messenger_lots_message_type_sets"><![CDATA[Общее сообщение]]></string>
+	<string name="_bx_messenger_lots_message_type_events"><![CDATA[Сообщение Событий]]></string>
+	<string name="_bx_messenger_lots_message_type_groups"><![CDATA[Сообщение Групп]]></string>
+	<string name="_bx_messenger_lots_message_type_broadcast"><![CDATA[Массовое сообщение]]></string>
+	<string name="_bx_messenger_lot_title_participants_number"><![CDATA[и ещё {0}]]></string>
+	<string name="_bx_messenger_server_is_not_installed"><![CDATA[Пожалуйста, укажите данные сервера для правильной работы чата]]></string>
+	<string name="_bx_messenger_reconnecting_message"><![CDATA[Подключение]]></string>
+	<string name="_bx_messenger_reconnecting_failed"><![CDATA[Связь потеряна]]></string>
+	<string name="_bx_messenger_upload_invalid_file_type"><![CDATA[Извините, но этот тип файла не может быть загружен из соображений безопасности!]]></string>
+	<string name="_bx_messenger_upload_delete"><![CDATA[Удалить]]></string>
+	<string name="_bx_messenger_upload_send_button"><![CDATA[Оправить]]></string>
+	<string name="_bx_messenger_upload_close"><![CDATA[Закрыть]]></string>
+	<string name="_bx_messenger_delete_confirm"><![CDATA[Вы уверены, что хотите удалить этот файл?]]></string>
+	<string name="_bx_messenger_post_view_actions"><![CDATA[Показать действия]]></string>
+	<string name="_bx_messenger_post_attache_files"><![CDATA[Прикрепить Файлы]]></string>
+	<string name="_bx_messenger_post_emoji"><![CDATA[Добавить смайл]]></string>
+	<string name="_bx_messenger_post_file_can_not_be_delete"><![CDATA[Вы не можете удалить этот файл!]]></string>
+	<string name="_bx_messenger_post_confirm_delete_file"><![CDATA[Вы уверены что хотите удалить этот файл?]]></string>
+	<string name="_bx_messenger_post_file_not_found"><![CDATA[Файл не найден!]]></string>
+	<string name="_bx_messenger_max_files_upload"><![CDATA[Максимальное количество файлов, которое можно отправить в одном сообщении]]></string>
+	<string name="_bx_messenger_max_files_upload_error"><![CDATA[Превышено максимальное количество файлов для отправки в одном сообщении (максимально {0})!]]></string>
+	<string name="_bx_messenger_invalid_server_response"><![CDATA[Неверный ответ сервера ({statusCode})]]></string>
+	<string name="_bx_messenger_attached_files_message"><![CDATA[Получено {0} файл(ов)]]></string>
+	<string name="_bx_messenger_lots_menu_settings_title"><![CDATA[Опции разговора]]></string>
+	<string name="_bx_messenger_lots_menu_back_title"><![CDATA[Вернуться к списку разговоров]]></string>
+	<string name="_bx_messenger_lots_menu_create_lot_title"><![CDATA[Создать разговор]]></string>
+	<string name="_bx_messenger_lots_menu_star_title"><![CDATA[Показать только помеченные/непомеченные разговоры]]></string>
+	<string name="_bx_messenger_search_for_lost_title"><![CDATA[Искать разговор по названию или содержанию сообщения]]></string>
+	<string name="_bx_messenger_repost_message"><![CDATA[получена ссылка на сообщение]]></string>
+	<string name="_bx_messenger_save_button"><![CDATA[Сохранить изменения]]></string>
+	<string name="_bx_messenger_cancel_button"><![CDATA[Отмена]]></string>
+	<string name="_bx_messenger_edit_by"><![CDATA[Редактировано {0}, {1}]]></string>
+	<string name="_bx_messenger_deleted_by"><![CDATA[Удалено {0}, пользователем {1}]]></string>
+	<string name="_bx_messenger_was_deleted"><![CDATA[Сообщение было удалено.]]></string>
+	<string name="_bx_messenger_confirm_delete_completely"><![CDATA[Удалить сообщение полностью?]]></string>
+	<string name="_bx_messenger_delete_message"><![CDATA[Удалить сообщение]]></string>
+	<string name="_bx_messenger_view_message"><![CDATA[Посмотреть удалённое сообщение]]></string>
+	<string name="_bx_messenger_upload_add_comment"><![CDATA[Добавить комментарий...]]></string>
+	<string name="_bx_messenger_start_record_button"><![CDATA[Старт]]></string>
+	<string name="_bx_messenger_play_record_button"><![CDATA[Воспроизвести]]></string>
+	<string name="_bx_messenger_send_record_button"><![CDATA[Отправить]]></string>
+	<string name="_bx_messenger_send_recorded_video"><![CDATA[Отправить записанное видео]]></string>
+	<string name="_bx_messenger_video_rec"><![CDATA[запись]]></string>
+	<string name="_bx_messenger_send_message_no_video"><![CDATA[Нет видео файла для отправки!]]></string>
+	<string name="_bx_messenger_close_video_confirm"><![CDATA[Если вы закроете окно, записанный видео файл будет потерян. Продолжить?]]></string>
+	<string name="_bx_messenger_max_video_file_size"><![CDATA[Максимальная длина видео файла для записи в минутах]]></string>
+	<string name="_bx_messenger_max_video_file_exceeds"><![CDATA[Извините, максимально разрешённая длина для видео файла {0} мин. превышена!]]></string>
+	<string name="_bx_messenger_video_not_supported"><![CDATA[Извините, но ваш браузер не поддерживает HTML5 видео WebM в VP8/VP9 или MP4 в H.264.]]></string>
+	<string name="_bx_messenger_jump_to_the_bottom"><![CDATA[Перейти к последнему сообщению]]></string>
+	<string name="_bx_messenger_mark_all_as_read"><![CDATA[Пометить все сообщения прочитанными]]></string>
+	<string name="_bx_messenger_search_no_results"><![CDATA[Поиск ничего не нашёл]]></string>
+	<string name="_bx_messenger_search_query_issue"><![CDATA[Появилась проблема при отправки запроса на сервер.]]></string>
+	<string name="_bx_messenger_search_desc"><![CDATA[Присоединился - {1}<br/>Последний раз был - {0}]]></string>
+	<string name="_bx_messenger_message"><![CDATA[Сообщение]]></string>
+	<string name="_bx_ntfs_alert_action_got_jot_ntfs_personal"><![CDATA[Новое сообщение в болталке]]></string>
+	<string name="_bx_messenger_txt_sample_comment_single"><![CDATA[сообщение - <b>{0}</b> в]]></string>
+	<string name="_bx_messenger_txt_sample_comment_file_single"><![CDATA[сообщение с <b>{0} файлом(и) </b> в]]></string>
+	<string name="_bx_messenger_max_ntfs_number"><![CDATA[Максимальное количество оповещений от одного участника в одном разговоре в течении часа]]></string>
+	<string name="_bx_messenger_max_parts_views"><![CDATA[Разрешать показывать просмотры сообщений в разговорах не превышающих указанное количество пользователей]]></string>
+	<string name="_bx_messenger_allow_to_remove_messages"><![CDATA[Разрешать пользователям модерировать собственные сообщения]]></string>
+	<string name="_bx_messenger_remove_messages_immediately"><![CDATA[Удалять сообщения сразу, без подтверждения администратора]]></string>
+	<string name="_bx_messenger_lots_menu_media"><![CDATA[Файлы]]></string>
+	<string name="_bx_messenger_no_permissions"><![CDATA[Вы не можете просматривать содержимое вкладки]]></string>
+	<string name="_bx_messenger_use_embedly"><![CDATA[Преобразовывать ссылки в сообщениях, использую выбранный Embeds провайдер]]></string>
+    <string name="_bx_messenger_giphy_api_key"><![CDATA[Giphy Api Key. Если Вы хотите подключить возможность использовать GIFs анимацию в сообщениях, вам нужно зарегистрироваться и получить Api Key тут https://developers.giphy.com/dashboard/?create=true]]></string>
+    <string name="_bx_messenger_giphy_type"><![CDATA[Использовать выбранный тип для анимации: GIFs или Стикеры (Стикеры это GIFs с прозрачным фотом)]]></string>
+    <string name="_bx_messenger_giphy_content_rating"><![CDATA[Рейтинг контента. Более подробно читать тут https://developers.giphy.com/docs/optional-settings#rating]]></string>
+    <string name="_bx_messenger_giphy_limit"><![CDATA[Отображать указанное количество картинок в блоке]]></string>
+    <string name="_bx_messenger_giphy_gifs_button"><![CDATA[Giphy]]></string>
+    <string name="_bx_messenger_giphy_gifs_nothing_found"><![CDATA[Ничего не найдено]]></string>
+	<string name="_bx_messenger_attached_giphy_message"><![CDATA[Вы получили анимацию]]></string>
+	<string name="_bx_messenger_max_drop_down_select"><![CDATA[Количество профайлов в результате поиска в выпадающем меню]]></string>
+	<string name="_bx_messenger_reaction_title_author"><![CDATA[Вы]]></string>
+	<string name="_bx_messenger_reaction_title"><![CDATA[{0} отреагировал: {1}]]></string>
+	<string name="_bx_messenger_uploading_file"><![CDATA[Загрузка]]></string>
+	<string name="_bx_messenger_upload_is_complete"><![CDATA[Загрузка файлов завершена]]></string>
+	<string name="_bx_messenger_upload_cancelled"><![CDATA[Запрос отменён]]></string>
+	<string name="_bx_messenger_uploading_remove_button"><![CDATA[Удалить]]></string>
+	<string name="_bx_messenger_wait_for_uploading"><![CDATA[Пожалуйста, подожди полной загрузки всех файлов!]]></string>
+	<string name="_bx_messenger_emoji_text_title"><![CDATA[Выбрать Эмоцию]]></string>
+	<string name="_bx_messenger_emoji_text_reaction"><![CDATA[Выбрать Реакцию]]></string>
+	<string name="_bx_messenger_emoji_search"><![CDATA[Поиск]]></string>
+	<string name="_bx_messenger_emoji_clear"><![CDATA[Очистить]]></string>
+	<string name="_bx_messenger_emoji_not_found"><![CDATA[Эмоция не найдена]]></string>
+	<string name="_bx_messenger_emoji_skintext"><![CDATA[Выберите оттенок кожи по умолчанию]]></string>
+	<string name="_bx_messenger_emoji_categ_search"><![CDATA[Результаты поиска]]></string>
+	<string name="_bx_messenger_emoji_categ_recent"><![CDATA[Часто используемые]]></string>
+	<string name="_bx_messenger_emoji_categ_people"><![CDATA[Улыбка & Люди]]></string>
+	<string name="_bx_messenger_emoji_categ_nature"><![CDATA[Животыне & Природа]]></string>
+	<string name="_bx_messenger_emoji_categ_foods"><![CDATA[Еда & Напитки]]></string>
+	<string name="_bx_messenger_emoji_categ_activity"><![CDATA[Активность]]></string>
+	<string name="_bx_messenger_emoji_categ_places"><![CDATA[Путешествия & Местность]]></string>
+	<string name="_bx_messenger_emoji_categ_objects"><![CDATA[Объекты]]></string>
+	<string name="_bx_messenger_emoji_categ_symbols"><![CDATA[Символы]]></string>
+	<string name="_bx_messenger_emoji_categ_flags"><![CDATA[Флаги]]></string>
+	<string name="_bx_messenger_emoji_categ_custom"><![CDATA[Пользовательские]]></string>
+	<string name="_bx_messenger_emoji_categorieslabel"><![CDATA[Категории]]></string>
+	<string name="_bx_messenger_emoji_skintones_1"><![CDATA[Оттенок кожи]]></string>
+	<string name="_bx_messenger_emoji_skintones_2"><![CDATA[Светлый тон]]></string>
+	<string name="_bx_messenger_emoji_skintones_3"><![CDATA[Средне-светлый тон]]></string>
+	<string name="_bx_messenger_emoji_skintones_4"><![CDATA[Средний тон]]></string>
+	<string name="_bx_messenger_emoji_skintones_5"><![CDATA[Смуглый тон]]></string>
+	<string name="_bx_messenger_emoji_skintones_6"><![CDATA[Тёмный тон]]></string>
+	<string name="_bx_messenger_emoji_set"><![CDATA[Выбрать набор эмоций для отображения реакций и в тексте сообщений]]></string>
+	<string name="_bx_messenger_reactions_size"><![CDATA[Размер иконки эмоций]]></string>
+	<string name="_bx_messenger_show_emoji_preview"><![CDATA[Отображать дополнительно выбранную эмоцию и тон кожи]]></string>
+	<string name="_bx_messenger_file_download"><![CDATA[Скачать файл]]></string>
+	<string name="_bx_messenger_txt_subobject_added_single"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> оправил(а) {subentry_sample}]]></string>
+	<string name="_bx_messenger_txt_sample_comment_giphy_single"><![CDATA[анимацию]]></string>
+	<string name="_bx_messenger_lots_menu_video_conf_start"><![CDATA[Начать видео конференцию]]></string>
+	<string name="_bx_messenger_lots_menu_video_conf_join"><![CDATA[Присоединиться к видео конференции]]></string>
+	<string name="_bx_messenger_jitsi_err_can_join_conference"><![CDATA[Извините, но вы не можете участвовать в этой конференции.]]></string>
+	<string name="_bx_messenger_jitsi_err_no_members"><![CDATA[Список участников пуст.]]></string>
+	<string name="_bx_messenger_jitsi_meet_app_title"><![CDATA[{0} видео конференция]]></string>
+	<string name="_bx_messenger_jitsi_meet_me"><![CDATA[Я]]></string>
+	<string name="_bx_messenger_jitsi_err_can_not_be_created"><![CDATA[Невозможно создать видео конференцию.]]></string>
+	<string name="_bx_messenger_jitsi_err_vc_was_not_found"><![CDATA[Видео конференция не найдена.]]></string>
+	<string name="_bx_messenger_jitsi_has_started"><![CDATA[Видео конференция началась <b>{0}</b>]]></string>
+	<string name="_bx_messenger_jitsi_conference"><![CDATA[<b>Видео конференция</b>, длительность - <b>{0}</b>]]></string>
+	<string name="_bx_messenger_jitsi_vc_duration"><![CDATA[{0} мин, {0} s.]]></string>
+	<string name="_bx_messenger_jitsi_vc_duration_h"><![CDATA[{0} ч, {1} мин, {2} сек.]]></string>
+	<string name="_bx_messenger_jitsi_vc_duration_m"><![CDATA[{0} мин, {1} сек.]]></string>
+	<string name="_bx_messenger_jitsi_vc_duration_s"><![CDATA[{0} секунд.]]></string>
+	<string name="_bx_messenger_jitsi_vc_into_title"><![CDATA[Сообщение было отправлено в конференции, созданной {0}, в {1}]]></string>
+	<string name="_bx_messenger_jitsi_vc_join"><![CDATA[Присоединиться]]></string>
+	<string name="_bx_messenger_are_you_sure_close_jisti"><![CDATA[Вы уверены, что хотите покинуть конференцию?]]></string>
+	<string name="_bx_messenger_allow_to_use_jitsi"><![CDATA[Разрешить создавать Jitsi конвенции]]></string>
+	<string name="_bx_messenger_jisti_connection_error"><![CDATA[Не могу подсоединиться в серверу конференций. Пожалуйста, проверьте интернет соединение или попробуете позже.]]></string>
+	<string name="_bx_messenger_jisti_not_active_conference"><![CDATA[Нет активных конференций. Пожалуйста, перезагрузите страницу или создайте собственную конференцию.]]></string>
+	<string name="_bx_messenger_jitsi_server"><![CDATA[Использовать этот домен в качестве Jitsi сервера]]></string>
+	<string name="_bx_messenger_jitsi_chat_enable"><![CDATA[Включить встроенный Jitsi чат внутри Jitsi конференции]]></string>
+	<string name="_bx_messenger_jitsi_chat_sync"><![CDATA[Синхронизировать Jitsi чат с историей Месенджера (в этом случае все сообщение из Jitsi чата будут дублироваться в Месенджере)]]></string>
+	<string name="_bx_messenger_jitsi_hide_info"><![CDATA[Показывать информацию о чате (в этом случае пользователи смогут видеть ссылку на чат, зная которую, можно присоединиться к конференции не зарегистрированным пользователям)]]></string>
+	<string name="_bx_messenger_jitsi_enable_only_for_private"><![CDATA[Разрешить конференции только для закрытых чатов]]></string>
+	<string name="_bx_messenger_jitsi_support_url"><![CDATA[В случае любых проблем внутри чата, пользователь будет перенаправлен на страницу по этой ссылки]]></string>
+	<string name="_bx_messenger_jitsi_watermark"><![CDATA[Включить водяной знак (<b>Замечание</b>: В случае если вы используете дефолтный Jitsi сервер, после включения это опции вы увидите Jitsi водяной знак). Можете прочитать больше о том как установить собственный сервер тут<a href="https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md">here</a>.]]></string>
+	<string name="_bx_messenger_jitsi_watermark_link"><![CDATA[Ссылка на водяном знаке (пользователь будет перенаправлен по это ссылке, если кликнет на водяной знак)]]></string>
+	<string name="_bx_messenger_jitsi_remote_user"><![CDATA[Участник]]></string>
+	<string name="_bx_messenger_lot_parts_error"><![CDATA[Разговор с таким же списком участников уже существует.]]></string>
+	<string name="_bx_messenger_jitsi_err_cant_type_use"><![CDATA[Извините, видео конференции для вас недоступны.]]></string>
+	<string name="_bx_messenger_file_is_too_large_error"><![CDATA[Файл имеет слишком большой размер!]]></string>
+	<string name="_bx_messenger_file_is_too_large_error_details"><![CDATA[Максимально допустимый размер файла {filesize}]]></string>
+	<string name="_bx_messenger_file_type_is_not_allowed"><![CDATA[Загрузка файлов данного формата запрещена!]]></string>
+	<string name="_bx_messenger_jitsi_mobile_warning"><![CDATA[Для того что бы сделать видео сигнал более стабильным и надёжным, вам необходимо подключиться через наше мибильное приложение (его можно скачать в app store) или через оригинальное приложение Jitsi. Продолжить с использованием Jitsi App?]]></string>
+	<string name="_bx_messenger_send_message_not_allowed"><![CDATA[Данная функция закрыта для вашего профиля!]]></string>
+	<string name="_bx_messenger_menu_video_conference_sys_title"><![CDATA[Видео Чат]]></string>
+	<string name="_bx_messenger_menu_video_conference_action_title"><![CDATA[Видео Чат]]></string>
+	<string name="_bx_messenger_acl_action_video_conference"><![CDATA[Создать персональный видео чат]]></string>
+	<string name="_bx_messenger_profile_not_found"><![CDATA[Профиль не найден!]]></string>
+	<string name="_bx_messenger_push_vc_bx_organizations_message_title"><![CDATA[Видео Конференция]]></string>
+	<string name="_bx_messenger_public_vc_bx_organizations_message"><![CDATA[{0} конференция началась]]></string>
+	<string name="_bx_messenger_push_vc_bx_persons_message_title"><![CDATA[Персональный видео звонок]]></string>
+	<string name="_bx_messenger_public_vc_bx_persons_message"><![CDATA[Видео звонок от {0}]]></string>
+	<string name="_bx_messenger_use_mentions"><![CDATA[Включить упоминания в соощениях]]></string>
+	<string name="_bx_messenger_loading"><![CDATA[загрузка...]]></string>
+	<string name="_bx_messenger_notification_subject_message"><![CDATA[Сообщение от {0}]]></string>
+	<string name="_bx_messenger_notification_subject_mention"><![CDATA[{0} упомянул Вас]]></string>
+	<string name="_bx_ntfs_alert_action_got_mention_ntfs_personal"><![CDATA[Упоминания в Болталке]]></string>
+	<string name="_bx_messenger_txt_subobject_added_message"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> послал {subentry_sample}]]></string>
+	<string name="_bx_messenger_txt_subobject_added_mention"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> упомянул вас в {subentry_sample}]]></string>
+	<string name="_bx_messenger_date_time_today"><![CDATA[Сегодня]]></string>
+	<string name="_bx_messenger_acl_action_create_talks"><![CDATA[Создавать разговоры]]></string>
+	<string name="_bx_messenger_acl_action_administrate_talks"><![CDATA[Модерировать разговоры (редактировать/удалять)]]></string>
+	<string name="_bx_messenger_acl_action_send_messages"><![CDATA[Оправлять сообщения]]></string>
+	<string name="_bx_messenger_acl_action_create_vc"><![CDATA[Создавать видео конференции]]></string>
+	<string name="_bx_messenger_disable_contact_privacy"><![CDATA[Отключить настройки контакта пользователя. <b>Замечание</b>: Если вы отключите настройки любой профиль будет иметь возможность контактировать с любым другим профилем!]]></string>
+	<string name="_bx_messenger_allow_to_moderate_messages"><![CDATA[Разрешать создателям чатов модерировать сообщения]]></string>	
+	<string name="_bx_messenger_clear_lot"><![CDATA[Вы уверено, что хотите безвозвратно удалить историю "{0}"?]]></string>	
+	<string name="_bx_messenger_enable_joined_organizations"><![CDATA[Разрешить организациям контактировать с пользователями только в случае, если пользователь является участником этой организации]]></string>
+	<string name="_bx_messenger_mark_as_unread"><![CDATA[Пометить как прочитанное]]></string>
+	<string name="_bx_messenger_menu_item_title_video_call"><![CDATA[Видео звонок]]></string>
+	<string name="_bx_messenger_menu_item_title_star"><![CDATA[Отметить чат как важный]]></string>
+	<string name="_bx_messenger_menu_item_title_mute"><![CDATA[Выключить звук]]></string>
+	<string name="_bx_messenger_menu_set_title_talk_menu"><![CDATA[Меню разговора]]></string>
+	<string name="_bx_messenger_acl_action_join_vc"><![CDATA[Присоединиться к видео конференции]]></string>
+	<string name="_bx_messenger_acl_action_join_personal_vc"><![CDATA[Присоединиться к персональной видео конференции]]></string>
+	<string name="_bx_messenger_video_recorder_is_not_available"><![CDATA[Видео запись недоступна для вашего браузера]]></string>
+	<string name="_bx_messenger_video_recorder_is_blocked"><![CDATA[Для того что бы воспользоваться этой возможностью, пожалуйста разрешите {0} доступ к вашей видео камере. Спасибо!]]></string>
+	<string name="_bx_messenger_acl_action_send_files"><![CDATA[Отправлять файлы]]></string>
+	<string name="_bx_messenger_show_friends"><![CDATA[Показывать по умолчанию список друзей, если участник не имеет созданных разговоров]]></string>
+	<string name="_bx_messenger_contact_privacy_not_allowed"><![CDATA[Участник разговора не разрешает посылать сообщения]]></string>
+	<string name="_bx_messenger_empty_history"><![CDATA[Пусто]]></string>
+	<string name="_bx_messenger_add_users_message"><![CDATA[Добавьте пользователей чтобы начать разговор]]></string>
+	<string name="_bx_messenger_check_toxic"><![CDATA[Блокировать сообщения с плохими словами. Опция работает только с установленным Antispam модулем.]]></string>
+	<string name="_bx_messenger_toxic_message"><![CDATA[Сообщение не может быть отправлено, так как противоречит стандартам нашего сообщества. Пожалуйста, используйте нормативную лексику!]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_inbox"><![CDATA[Непрочитанные]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_threads"><![CDATA[Обсуждения]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_direct"><![CDATA[Прямые разговоры]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_files"><![CDATA[Файлы]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_saved"><![CDATA[Сохраненные сообщения]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_favorites"><![CDATA[Избранные чаты]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_reply"><![CDATA[Ответы]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_mr"><![CDATA[Реакции и Упоминания]]></string>
+	<string name="_bx_messenger_menu_item_title_parent_lot"><![CDATA[Open original message]]></string>
+	<string name="_bx_messenger_jot_server_jwt"><![CDATA[Секретное слово для включения JWT токена с Jot Server. Установите то же самое <b>secret</b> слово в файле config/config.json Jot Server, чтобы включить эту опцию. Это позволит подключаться к Jot Server только с авторизованных сайтов. Оставьте это поле пустым, если вы не хотите использовать JWT токены с Jot Server.]]></string>
+	<string name="_bx_messenger_nav_menu_title_view"><![CDATA[Main menu]]></string>
+	<string name="_bx_messenger_groups_menu_title_view"><![CDATA[Groups Menu]]></string>
+	<string name="_bx_messenger_menu_title_view"><![CDATA[Menu заголовка разговора]]></string>
+	<string name="_bx_messenger_page_main_messenger_block"><![CDATA[Main Messenger page]]></string>
+	<string name="_bx_messenger_pages"><![CDATA[Pages]]></string>
+	<string name="_bx_messenger_menu_lot_info_title"><![CDATA[Инфо меню разговора]]></string>
+	<string name="_bx_messenger_menu_set_title_talk_info_menu"><![CDATA[Инфо меню разговора]]></string>
+	<string name="_bx_messenger_menu_set_title_jot_menu"><![CDATA[Меню сообщения]]></string>
+	<string name="_bx_messenger_jot_menu_reaction"><![CDATA[Добавить реакцию]]></string>
+	<string name="_bx_messenger_jot_menu_reply"><![CDATA[Ответить]]></string>
+	<string name="_bx_messenger_reply_audio_file"><![CDATA[Аудио файл - "{0}"]]></string>
+	<string name="_bx_messenger_jot_menu_edit"><![CDATA[Редактировать сообщение]]></string>
+	<string name="_bx_messenger_jot_menu_remove"><![CDATA[Удалить запись]]></string>
+	<string name="_bx_messenger_jot_menu_thread"><![CDATA[Ответить отдельно]]></string>
+	<string name="_bx_messenger_jot_menu_share"><![CDATA[Копировать ссылку на сообщение]]></string>
+	<string name="_bx_messenger_jot_menu_save"><![CDATA[Сохранить сообщение]]></string>
+	<string name="_bx_messenger_bad_request"><![CDATA[Запрос неверный!]]></string>
+	<string name="_bx_messenger_search_criteria_list"><![CDATA[Используйте выделенные критерии для поиска]]></string>
+	<string name="_bx_messenger_search_item_titles"><![CDATA[заголовки разговоров]]></string>
+	<string name="_bx_messenger_search_item_participants"><![CDATA[имена участников]]></string>
+	<string name="_bx_messenger_search_item_content"><![CDATA[тело сообщений]]></string>
+	<string name="_bx_messenger_time_in_history"><![CDATA[Используйте формат времени <b>from now</b> для отображения в сообщениях]]></string>
+	<string name="_bx_messenger_dont_show_search_desc"><![CDATA[Не показывать описание профилей во всплывающем меню поиска контактов]]></string>
+	<string name="_bx_messenger_use_unique_mode"><![CDATA[Включить двухколоночный режим]]></string>
+	<string name="_bx_messenger_talk_is_not_allowed"><![CDATA[Разговор недоступен для вас!]]></string>
+	<string name="_bx_messenger_connect_friends_only"><![CDATA[Разрешить создавать разговоры только с друзьями]]></string>
+	<string name="_bx_messenger_talk_types_market_title"><![CDATA[Разговор с {opponent}]]></string>
+	<string name="_bx_messenger_are_you_sure_leave"><![CDATA[Вы уверены, что хотите покинуть "{0}" ?]]></string>
+	<string name="_bx_messenger_page_unknown"><![CDATA[Неизвестная]]></string>
+	<string name="_bx_messenger_txt_sample_comment_reply_single"><![CDATA[ответ]]></string>
+	<string name="_bx_messenger_txt_sample_comment_repost_single"><![CDATA[репост]]></string>
+	<string name="_bx_messenger_txt_sample_email_push"><![CDATA[прислал(а) {0}]]></string>
+	<string name="_bx_messenger_lot_parts_empty"><![CDATA[Пожалуйста укажите хотя бы одного участника разговора!]]></string>
+	<string name="_bx_messenger_dont_update_title"><![CDATA[Не обновлять заголовок страницы при поступлении новых сообщений]]></string>
+	<string name="_bx_messenger_acl_action_edit_messages"><![CDATA[Редактировать сообщения]]></string>
+	<string name="_bx_messenger_acl_action_delete_messages"><![CDATA[Удалять сообщения]]></string>
+	<string name="_bx_messenger_files_can_not_be_uploaded"><![CDATA[Файлы не могут быть добавлены в этот разговор!]]></string>
+	<string name="_bx_messenger_not_allowed_to_read"><![CDATA[Вы не можете прочитать сообщение, так как не являетесь участником разговора!]]></string>
+	<string name="_bx_messenger_not_found_to_read"><![CDATA[Разговор с сообщением не был найден, пожалуйста уточните ссылку!]]></string>
+	<string name="_bx_messenger_menu_jot_title"><![CDATA[Меню сообщения]]></string>
+	<string name="_bx_messenger_share_jot"><![CDATA[Копировать ссылку]]></string>
+	<string name="_bx_messenger_left_menu_groups_item_create_group"><![CDATA[Создать группу]]></string>
+	<string name="_bx_messenger_empty_users_list"><![CDATA[Профайлы не найдены]]></string>
+	<string name="_bx_messenger_users_creation_new_message"><![CDATA[Новое сообщение...]]></string>
+	<string name="_bx_messenger_groups_name"><![CDATA[Название]]></string>
+	<string name="_bx_messenger_groups_name_info"><![CDATA[Название группы доступное только для участников]]></string>
+	<string name="_bx_messenger_groups_desc"><![CDATA[Описание]]></string>
+	<string name="_bx_messenger_groups_desc_info"><![CDATA[Опишите группу, это поможет пользователям найти её]]></string>
+	<string name="_bx_messenger_groups_privacy"><![CDATA[Членства]]></string>
+	<string name="_bx_messenger_groups_privacy_info"><![CDATA[Группы будет видна только отмеченным членствам]]></string>
+	<string name="_bx_messenger_groups_create_form_title"><![CDATA[Создать группу]]></string>
+	<string name="_bx_messenger_groups_edit_form_title"><![CDATA[Редактировать Группу]]></string>
+	<string name="_bx_messenger_form_entry_input_allow_view_groups_to"><![CDATA[Разрешить показывать группу]]></string>
+	<string name="_bx_messenger_nothing_has_been_saved"><![CDATA[Данные не сохранены!]]></string>
+	<string name="_bx_messenger_threads_title"><![CDATA[Связанные разговоры]]></string>
+	<string name="_bx_messenger_nothing_found"><![CDATA[Ничего не найдено]]></string>
+	<string name="_bx_messenger_jot_replies"><![CDATA[Ответы]]></string>
+	<string name="_bx_messenger_view_replies"><![CDATA[Посмотреть ответы]]></string>
+	<string name="_bx_messenger_menu_item_title_group_list"><![CDATA[Список разговоров]]></string>
+	<string name="_bx_messenger_groups_talks_title"><![CDATA[Список разговоров]]></string>
+	<string name="_bx_messenger_list_area_inbox"><![CDATA[Сообщения]]></string>
+	<string name="_bx_messenger_text_area_topic_title"><![CDATA[Заголовок]]></string>
+	<string name="_bx_messenger_left_menu_item_direct_messages"><![CDATA[Разговоры]]></string>
+	<string name="_bx_messenger_left_menu_item_threads"><![CDATA[Связанные разговоры]]></string>
+	<string name="_bx_messenger_left_menu_item_pages"><![CDATA[Страницы]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_replies"><![CDATA[Ответы]]></string>
+	<string name="_bx_messenger_lots_menu_clear"><![CDATA[Очистить историю]]></string>
+	<string name="_bx_messenger_lots_menu_settings"><![CDATA[Настройки]]></string>
+	<string name="_bx_messenger_view_in_chat"><![CDATA[Посмотреть внутри разговора]]></string>
+	<string name="_bx_messenger_neo_app_form"><![CDATA[Форма для отправки сообщений]]></string>
+	<string name="_bx_messenger_neo_app_form_display_send_message"><![CDATA[Отправить сообщение]]></string>
+	<string name="_bx_messenger_neo_app_form_input_submit"><![CDATA[Отправить]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_submit"><![CDATA[Отправить]]></string>
+	<string name="_bx_messenger_neo_app_form_input_cancel"><![CDATA[Отменить]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_cancel"><![CDATA[Отменить]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_files"><![CDATA[Файл]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_message"><![CDATA[Сообщение]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_parent_id"><![CDATA[ID родителя]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_message_id"><![CDATA[ID сообщения]]></string>
+    <string name="_bx_messenger_page_block_contacts_title"><![CDATA[Контакты]]></string>
+	<string name="_bx_messenger_profile_title_snippet_meta"><![CDATA[Мета Информация]]></string>
+	<string name="_bx_messenger_set_title_profile_snippet_meta"><![CDATA[Метаданные профиля для блока контактов]]></string>
+	<string name="_bx_messenger_item_title_sm_message"><![CDATA[Сообщение]]></string>
+	<string name="_bx_messenger_broadcast_fields"><![CDATA[Поля для фильтрации при создании массовых сообщений]]></string>
+	<string name="_bx_messenger_broadcast_field_membership"><![CDATA[Уровень участника]]></string>
+	<string name="_bx_messenger_broadcast_profile_fields_title"><![CDATA[Пожалуйста выбирите поля для фильтрации]]></string>
+	<string name="_bx_messenger_broadcast_field_calculate"><![CDATA[Расчитать]]></string>
+	<string name="_bx_messenger_broadcast_total_profiles"><![CDATA[Сообщение получат {0} участников]]></string>
+	<string name="_bx_messenger_lots_menu_info"><![CDATA[Информация]]></string>
+
+	<string name="_bx_messenger_create_convo_menu_standard_system"><![CDATA[Группа]]></string>
+	<string name="_bx_messenger_create_convo_menu_friends_system"><![CDATA[Друзья]]></string>
+	<string name="_bx_messenger_create_convo_menu_broadcast_system"><![CDATA[Массовая рассылка]]></string>
+	<string name="_bx_messenger_create_convo_menu_standard"><![CDATA[Группа]]></string>
+	<string name="_bx_messenger_create_convo_menu_friends"><![CDATA[Друзья]]></string>
+	<string name="_bx_messenger_create_convo_menu_broadcast"><![CDATA[Массовая рассылка]]></string>
+	<string name="_bx_messenger_create_convo_menu_title"><![CDATA[Создать разговоры]]></string>
+	<string name="_bx_messenger_create_convo_menu_set_title"><![CDATA[Создать разговоры]]></string>
+	<string name="_bx_messenger_create_convo_template_title"><![CDATA[Шаблона для меню создания разговоров]]></string>
+
+	<string name="_bx_messenger_filter_criteria_membership"><![CDATA[Уровень членства]]></string>
+
+	<string name="_bx_messenger_broadcast_field_email"><![CDATA[Почта]]></string>
+	<string name="_bx_messenger_broadcast_field_push"><![CDATA[Пуш уведомление]]></string>
+	<string name="_bx_messenger_broadcast_field_site"><![CDATA[Сайт]]></string>
+	<string name="_bx_messenger_broadcast_field_notify"><![CDATA[Уведомлять участника]]></string>
+	<string name="_bx_messenger_broadcast_field_notify_info"><![CDATA[Участники будет уведомлены, используя выбранные методы]]></string>
+	<string name="_bx_messenger_broadcast_field_author"><![CDATA[Автор рассылки]]></string>
+	<string name="_bx_messenger_broadcast_field_author_info"><![CDATA[Если не указать автора, то будет текущий залогиненный пользователь]]></string>
+	<string name="_bx_messenger_broadcast_message_title"><![CDATA[Заголовок сообщения]]></string>
+	<string name="_bx_messenger_broadcast_message_body"><![CDATA[Тело сообщения]]></string>
+	<string name="_bx_messenger_txt_subobject_added_broadcast"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> создал рассылку {subentry_sample}]]></string>
+	<string name="_bx_messenger_notification_subject_broadcast"><![CDATA[Рассылка от {0}]]></string>
+
+	<string name="_bx_messenger_filter_criteria_additional_message"><![CDATA[Дополнительные профайл вовлеченные в массовую рассылку]]></string>
+	<string name="_bx_messenger_filter_criteria_friends_message"><![CDATA[У вас {0} друзей для участия в рассылки]]></string>
+	<string name="_bx_messenger_filter_criteria_followers_message"><![CDATA[У вас {0} подписчиков для участия в рассылки]]></string>
+	<string name="_bx_messenger_filter_criteria_on_friends"><![CDATA[У вас нет друзей для участия в рассылке]]></string>
+	<string name="_bx_messenger_filter_criteria_on_followers"><![CDATA[У вас нет подписчиков для участия в рассылке]]></string>
+	<string name="_bx_messenger_acl_action_create_broadcast"><![CDATA[Создать рассылку]]></string>
+
+	<string name="_bx_messenger_convo_info_participants"><![CDATA[Участники]]></string>
+	<string name="_bx_messenger_convo_info_messages"><![CDATA[Сообщения]]></string>
+	<string name="_bx_messenger_convo_info_files"><![CDATA[Файлы]]></string>
+	<string name="_bx_messenger_filter_criteria_form"><![CDATA[Форма с критериями для массовых рассылок]]></string>
+	<string name="_bx_messenger_filter_criteria_form_display"><![CDATA[Форма с критериями для массовых рассылок]]></string>
+	<string name="_bx_messenger_no_criteria_available"><![CDATA[Критерии для фильтрации не найдены!]]></string>
+	<string name="_bx_messenger_create_talk_confirm"><![CDATA[Вы уверены, что хотите создать разговор?]]></string>
+	<string name="_bx_messenger_neo_app_form_input_caption_reply"><![CDATA[Номер родительского сообщения]]></string>
+	<string name="_bx_messenger_no_server"><![CDATA[В настройках мессенджера не определен URL-адрес Jot Server. В настоящее время связь в режиме реального времени невозможна.]]></string>
+	<string name="_bx_messenger_jot_menu_remove_save"><![CDATA[Удалить из сохранённых]]></string>
+
+	<string name="_bx_messenger_cpt_category_settings"><![CDATA[Настройки]]></string>
+	<string name="_bx_messenger_quill_toolbar_settings"><![CDATA[Инструменты редактора Quill (JSON массив)]]></string>
+	<string name="_bx_messenger_cpt_category_security"><![CDATA[Безопасность]]></string>
+	<string name="_bx_messenger_cpt_category_moderation"><![CDATA[Модерация]]></string>
+	<string name="_bx_messenger_cpt_category_giphy"><![CDATA[Giphy]]></string>
+	<string name="_bx_messenger_cpt_category_emoji"><![CDATA[Emoji]]></string>
+	<string name="_bx_messenger_cpt_category_jitsi"><![CDATA[Jitsi]]></string>
+	<string name="_bx_messenger_cpt_category_privacy"><![CDATA[Приватность]]></string>
+	<string name="_bx_messenger_cpt_category_view"><![CDATA[Вид]]></string>
+	<string name="_bx_messenger_cpt_category_search"><![CDATA[Поиск]]></string>
+
+	<string name="_bx_messenger_hide_parts"><![CDATA[Скрыть иконки последних ответивших участников в столбце разговора]]></string>
+	<string name="_bx_messenger_show_search_box"><![CDATA[Показывать окно поиска в столбце разговоров открытым, по умолчанию]]></string>
+
+	<string name="_bx_messenger_giphy_powered_by"><![CDATA[Включение логотипа 'Powered By': обязательный шаг для интеграции Giphy в производственной среде]]></string>
+	<string name="_bx_messenger_video_record_is_not_supported"><![CDATA[Водео запись не поддерживается: {0}]]></string>
+	<string name="_bx_messenger_nav_menu_item_title_groups"><![CDATA[Группы]]></string>
+
+	<string name=""><![CDATA[]]></string>
+</resources>

+ 16 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/sql/enable-app.sql

@@ -0,0 +1,16 @@
+-- PAGES: config_api
+
+UPDATE `sys_objects_page` SET `config_api`='{"layout":"messenger"}' WHERE `object`='bx_messenger_main';
+
+-- PAGES: active_api
+
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_messenger_main' AND `module`='bx_messenger' AND `title`='_bx_messenger_page_main_messenger_block';
+
+-- MENUS: active_api
+
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_messenger_nav_menu' AND `module`='bx_messenger' AND `name`='saved';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_messenger_nav_menu' AND `module`='bx_messenger' AND `name`='reply';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_messenger_nav_menu' AND `module`='bx_messenger' AND `name`='threads';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_messenger_nav_menu' AND `module`='bx_messenger' AND `name`='inbox';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_messenger_nav_menu' AND `module`='bx_messenger' AND `name`='direct';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_messenger_nav_menu' AND `module`='bx_messenger' AND `name`='favorites';

+ 385 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/install/sql/enable.sql

@@ -0,0 +1,385 @@
+SET @sName = 'bx_messenger';
+
+-- SETTINGS
+SET @iTypeOrder = (SELECT MAX(`order`) FROM `sys_options_types` WHERE `group` = 'modules');
+INSERT INTO `sys_options_types`(`group`, `name`, `caption`, `icon`, `order`) VALUES 
+('modules', @sName, '_bx_messenger', 'bx_messenger@modules/boonex/messenger/|std-icon.svg', IF(ISNULL(@iTypeOrder), 1, @iTypeOrder + 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories`(`type_id`, `name`, `caption`, `hidden`, `order`) VALUES (@iTypeId, CONCAT(@sName, '_settings'), '_bx_messenger_cpt_category_settings', 0, 1);
+SET @iCategId = LAST_INSERT_ID();
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_messenger_server_url', '', @iCategId, '_bx_messenger_server_url', 'digit', '', '', '', 1),
+('bx_messenger_max_symbols_number', 64000, @iCategId, '_bx_messenger_symbols_num_option', 'digit', '', '', '', 2),
+('bx_messenger_max_files_send', 5, @iCategId, '_bx_messenger_max_files_upload', 'digit', '', '', '', 3),
+('bx_messenger_max_video_length_minutes', 5, @iCategId, '_bx_messenger_max_video_file_size', 'digit', '', '', '', 4),
+('bx_messenger_max_ntfs_number', 5, @iCategId, '_bx_messenger_max_ntfs_number', 'digit', '', '', '', 5),
+('bx_messenger_max_parts_views', 10, @iCategId, '_bx_messenger_max_parts_views', 'digit', '', '', '', 6),
+('bx_messenger_use_embedly', 'on', @iCategId, '_bx_messenger_use_embedly', 'checkbox', '', '', '', 7),
+('bx_messenger_enable_mentions', 'on', @iCategId, '_bx_messenger_use_mentions', 'checkbox', '', '', '', 8),
+('bx_messenger_broadcast_fields', 'membership,gender,location,birthday', @iCategId, '_bx_messenger_broadcast_fields', 'list', '', '', 'a:2:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:20:"get_broadcast_fields";}', 9),
+('bx_messenger_quill_toolbar', '[[''bold'', ''italic'', ''underline'', ''strike'', ''link''],[''blockquote'', ''code-block''],[{''color'':[]},{''background'':[]}]]', @iCategId, '_bx_messenger_quill_toolbar_settings', 'digit', '', '', '', 10);
+
+INSERT INTO `sys_options_categories`(`type_id`, `name`, `caption`, `hidden`, `order`) VALUES (@iTypeId, CONCAT(@sName, '_security'), '_bx_messenger_cpt_category_security', 0, 2);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_messenger_check_toxic', '', @iCategId, '_bx_messenger_check_toxic', 'checkbox', '', '', '', 1),
+('bx_messenger_jot_server_jwt', '', @iCategId, '_bx_messenger_jot_server_jwt', 'digit', '', '', '', 2);
+
+INSERT INTO `sys_options_categories`(`type_id`, `name`, `caption`, `hidden`, `order`) VALUES (@iTypeId, CONCAT(@sName, '_moderation'), '_bx_messenger_cpt_category_moderation', 0, 3);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_messenger_allow_to_remove_messages', 'on', @iCategId, '_bx_messenger_allow_to_remove_messages', 'checkbox', '', '', '', 1),
+('bx_messenger_allow_to_moderate_messages', 'on', @iCategId, '_bx_messenger_allow_to_moderate_messages', 'checkbox', '', '', '', 2),
+('bx_messenger_remove_messages_immediately', '', @iCategId, '_bx_messenger_remove_messages_immediately', 'checkbox', '', '', '', 3);
+
+INSERT INTO `sys_options_categories`(`type_id`, `name`, `caption`, `hidden`, `order`) VALUES (@iTypeId, CONCAT(@sName, '_emoji'), '_bx_messenger_cpt_category_emoji', 0, 4);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_messenger_emoji_set', 'native', @iCategId, '_bx_messenger_emoji_set', 'select', '', '', 'native,apple,google,twitter,facebook', 1),
+('bx_messenger_reactions_size', 16, @iCategId, '_bx_messenger_reactions_size', 'select', '', '', '16,20,24,32', 2),
+('bx_messenger_show_emoji_preview', '', @iCategId, '_bx_messenger_show_emoji_preview', 'checkbox', '', '', '', 3);
+
+INSERT INTO `sys_options_categories`(`type_id`, `name`, `caption`, `hidden`, `order`) VALUES (@iTypeId, CONCAT(@sName, '_giphy'), '_bx_messenger_cpt_category_giphy', 0, 5);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_messenger_giphy_key', '', @iCategId, '_bx_messenger_giphy_api_key', 'digit', '', '', '', 1),
+('bx_messenger_giphy_type', 'gifs', @iCategId, '_bx_messenger_giphy_type', 'select', '', '', 'gifs,stickers', 2),
+('bx_messenger_giphy_content_rating', 'g', @iCategId, '_bx_messenger_giphy_content_rating', 'select', '', '', 'g,pg,pg-13,r', 3),
+('bx_messenger_giphy_limit', 15, @iCategId, '_bx_messenger_giphy_limit', 'digit', '', '', '', 5),
+('bx_messenger_giphy_powered_by', '', @iCategId, '_bx_messenger_giphy_powered_by', 'checkbox', '', '', '', 6);
+
+INSERT INTO `sys_options_categories`(`type_id`, `name`, `caption`, `hidden`, `order`) VALUES (@iTypeId, CONCAT(@sName, '_jitsi'), '_bx_messenger_cpt_category_jitsi', 0, 6);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_messenger_jitsi_enable', 'on', @iCategId, '_bx_messenger_allow_to_use_jitsi', 'checkbox', '', '', '', 1),
+('bx_messenger_jitsi_server', 'meet.jit.si', @iCategId, '_bx_messenger_jitsi_server', 'digit', '', '', '', 2),
+('bx_messenger_jitsi_chat', '', @iCategId, '_bx_messenger_jitsi_chat_enable', 'checkbox', '', '', '', 3),
+('bx_messenger_jitsi_sync', '', @iCategId, '_bx_messenger_jitsi_chat_sync', 'checkbox', '', '', '', 4),
+('bx_messenger_jitsi_hide_info', '', @iCategId, '_bx_messenger_jitsi_hide_info', 'checkbox', '', '', '', 5),
+('bx_messenger_jitsi_enable_watermark', '', @iCategId, '_bx_messenger_jitsi_watermark', 'checkbox', '', '', '', 6),
+('bx_messenger_jitsi_watermark_link', '', @iCategId, '_bx_messenger_jitsi_watermark_link', 'digit', '', '', '', 7),
+('bx_messenger_jitsi_only_for_private', 'on', @iCategId, '_bx_messenger_jitsi_enable_only_for_private', 'checkbox', '', '', '', 8),
+('bx_messenger_jwt_app_id', '', @iCategId, '_bx_messenger_jwt_app_id', 'digit', '', '', '', 9),
+('bx_messenger_jwt_app_secret', '', @iCategId, '_bx_messenger_jwt_app_secret', 'digit', '', '', '', 10),
+('bx_messenger_jitsi_support_url', 'https://community.jitsi.org/', @iCategId, '_bx_messenger_jitsi_support_url', 'digit', '', '', '', 11);
+
+INSERT INTO `sys_options_categories`(`type_id`, `name`, `caption`, `hidden`, `order`) VALUES (@iTypeId, CONCAT(@sName, '_privacy'), '_bx_messenger_cpt_category_privacy', 0, 7);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_messenger_enable_joined_organizations', '', @iCategId, '_bx_messenger_enable_joined_organizations', 'checkbox', '', '', '', 1),
+('bx_messenger_disable_contact_privacy', '', @iCategId, '_bx_messenger_disable_contact_privacy', 'checkbox', '', '', '', 2),
+('bx_messenger_connect_friends_only', '', @iCategId, '_bx_messenger_connect_friends_only', 'checkbox', '', '', '', 3);
+
+INSERT INTO `sys_options_categories`(`type_id`, `name`, `caption`, `hidden`, `order`) VALUES (@iTypeId, CONCAT(@sName, '_search'), '_bx_messenger_cpt_category_search', 0, 8);
+SET @iCategId = LAST_INSERT_ID();
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_messenger_dont_show_search_desc', '', @iCategId, '_bx_messenger_dont_show_search_desc', 'checkbox', '', '', '', 1),
+('bx_messenger_search_criteria', 'titles,participants,content', @iCategId, '_bx_messenger_search_criteria_list', 'list', '', '', 'a:2:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:18:"get_search_options";}', 2);
+
+INSERT INTO `sys_options_categories`(`type_id`, `name`, `caption`, `hidden`, `order`) VALUES (@iTypeId, CONCAT(@sName, '_view'), '_bx_messenger_cpt_category_view', 0, 9);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_messenger_max_symbols_brief_jot', 145, @iCategId, '_bx_messenger_symbols_num_brief_jot', 'digit', '', '', '', 1),
+('bx_messenger_max_jot_number_default', 20, @iCategId, '_bx_messenger_jot_number_default', 'digit', '', '', '', 2),
+('bx_messenger_max_jot_number_in_history', 30, @iCategId, '_bx_messenger_max_jot_number_in_history', 'digit', '', '', '', 3),
+('bx_messenger_max_lots_number', 15, @iCategId, '_bx_messenger_max_max_lots_number', 'digit', '', '', '', 4),
+('bx_messenger_max_drop_down_select', 5, @iCategId, '_bx_messenger_max_drop_down_select', 'digit', '', '', '', 5),
+('bx_messenger_time_in_history', '', @iCategId, '_bx_messenger_time_in_history', 'checkbox', '', '', '', 6),
+('bx_messenger_use_unique_mode', '', @iCategId, '_bx_messenger_use_unique_mode', 'checkbox', '', '', '', 7),
+('bx_messenger_show_friends', 'on', @iCategId, '_bx_messenger_show_friends', 'checkbox', '', '', '', 8),
+('bx_messenger_dont_update_title', '', @iCategId, '_bx_messenger_dont_update_title', 'checkbox', '', '', '', 9),
+('bx_messenger_hide_parts', '', @iCategId, '_bx_messenger_hide_parts', 'checkbox', '', '', '', 10),
+('bx_messenger_show_search_box', '', @iCategId, '_bx_messenger_show_search_box', 'checkbox', '', '', '', 11);
+
+-- MENU: notifications
+SET @iMIOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name` = 'sys_toolbar_member' AND `order` < 9999);
+INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`, `visibility_custom`, `hidden_on`) VALUES
+('sys_toolbar_member', @sName, 'notifications-messenger', '_bx_messenger_menu_notifications_item_sys_title', '', 'page.php?i=messenger', '', '', 'comments col-green1', 'a:2:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:20:"get_updated_lots_num";}', '', 2147483646, 1, 1, 0, '', 9),
+('trigger_profile_view_actions', @sName, 'messenger', '_bx_messenger_menu_new_chat_sys_title', '_bx_messenger_menu_new_chat_action_title', 'page.php?i=messenger&profile_id={profile_id}', '', '', 'comments', '', '', 2147483646, 1, 0, 0, 'a:3:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:18:"is_contact_allowed";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', ''),
+('trigger_profile_view_actions', @sName, 'public-vc-messenger', '_bx_messenger_menu_video_conference_sys_title', '_bx_messenger_menu_video_conference_action_title', 'javascript:void(0)', 'javascript:oMUtils.showConferenceWindow(''modules/?r=messenger/get_video_conference_form/{profile_id}'')', '', 'video', '', '', 2147483646, 1, 0, 0, 'a:3:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:27:"is_video_conference_allowed";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', '');
+
+SET @iMIOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name` = 'sys_account_notifications' AND `order` < 9999);
+INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`, `visibility_custom`, `hidden_on`) VALUES
+('sys_account_notifications', @sName, 'notifications-messenger', '_bx_messenger_menu_notifications_item_sys_title', '_bx_messenger_menu_notifications_item_title', 'page.php?i=messenger', '', '', 'comments col-green1', 'a:2:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:20:"get_updated_lots_num";}', '', 2147483646, 1, 1, @iMIOrder + 2, '', 6);
+
+-- PAGE: module home
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`, `cover`) VALUES 
+('bx_messenger_main', '_bx_messenger_page_title_sys_main', '_bx_messenger_page_title_main', @sName, 5, 2147483647, 1, 'messenger', 'page.php?i=messenger', '', '', '', 0, 1, 0, 'BxMessengerPageMain', 'modules/boonex/messenger/classes/BxMessengerPageMain.php', 0);
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_messenger_main', 1, @sName, '_bx_messenger_page_main_messenger_block', 0, 2147483647, 'service', 'a:2:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:23:"get_main_messenger_page";}', 0, 0, 0);
+
+-- PAGES: add page block to profiles modules (trigger* page objects are processed separately upon modules enable/disable)
+SET @iPBCellProfile = 2;
+SET @iPBCellGroup = 4;
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `tabs`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`, `active`) VALUES
+('trigger_page_profile_view_entry', @iPBCellProfile, @sName, '_bx_messenger_page_block_title_messenger', 11, 1, 2147483647, 'service', 'a:3:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:19:"get_block_messenger";s:6:"params";a:1:{i:0;s:6:"{type}";}}', 0, 0, 0, 0),
+('trigger_page_group_view_entry', @iPBCellGroup, @sName, '_bx_messenger_page_block_title_messenger', 11, 1, 2147483647, 'service', 'a:3:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:19:"get_block_messenger";s:6:"params";a:1:{i:0;s:6:"{type}";}}', 0, 0, 0, 0);
+
+-- PAGE: service blocks
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `tabs`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`, `active`) VALUES
+('', 0, @sName, '_bx_messenger_page_block_title_messenger', 11, 1, 2147483647, 'service', 'a:3:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:19:"get_block_messenger";s:6:"params";a:1:{i:0;s:6:"{type}";}}', 1, 1, 0, 0);
+
+-- PAGE: service my contacts block
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `tabs`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`, `active`) VALUES
+('', 0, @sName, '_bx_messenger_page_block_contacts_title', 11, 1, 2147483647, 'service', 'a:2:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:28:"get_block_contacts_messenger";}}', 1, 1, 0, 0);
+
+-- PAGES: add page block on home
+SET @iPBCellHome = 1;
+SET @iPBOrderHome = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_pages_blocks` WHERE `object` = 'sys_home' AND `cell_id` = @iPBCellHome ORDER BY `order` DESC LIMIT 1);
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `tabs`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`, `active`) VALUES
+('sys_home', @iPBCellHome, @sName, '_bx_messenger_home_page_all_members_block', 11, 1, 2147483647, 'service', 'a:3:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:19:"get_block_messenger";s:6:"params";a:1:{i:0;s:7:"members";}}', 0, 0, @iPBOrderHome + 1, 0);
+
+-- ALERTS
+INSERT INTO `sys_alerts_handlers` (`name`, `class`, `file`, `service_call`) VALUES 
+('bx_messenger', '', '', 'a:2:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:24:"delete_history_by_author";}');
+SET @iHandler := LAST_INSERT_ID();
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('profile', 'delete', @iHandler);
+
+-- ALERTS
+INSERT INTO `sys_alerts_handlers` (`name`, `class`, `file`, `service_call`) VALUES
+('bx_messenger_actions', '', '', 'a:2:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:8:"response";}');
+SET @iHandler := LAST_INSERT_ID();
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('bx_groups', 'fan_added', @iHandler),
+('bx_events', 'fan_added', @iHandler),
+('bx_organizations', 'fan_added', @iHandler),
+('bx_spaces', 'fan_added', @iHandler),
+('bx_events_fans','connection_removed', @iHandler),
+('bx_groups_fans','connection_removed', @iHandler),
+('bx_organizations_fans','connection_removed', @iHandler),
+('bx_spaces_fans','connection_removed', @iHandler);
+
+-- LIVE UPDATES
+INSERT INTO `sys_objects_live_updates`(`name`, `frequency`, `service_call`, `active`) VALUES
+('bx_messenger_new_messages', 1, 'a:3:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:16:"get_live_updates";s:6:"params";a:3:{i:0;a:0:{}i:1;a:2:{s:11:"menu_object";s:18:"sys_toolbar_member";s:9:"menu_item";s:23:"notifications-messenger";}i:2;s:7:"{count}";}}', 1);
+
+INSERT INTO `sys_objects_live_updates`(`name`, `frequency`, `service_call`, `active`) VALUES
+('bx_messenger_public_video_conference', 1, 'a:3:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:19:"get_live_vc_updates";s:6:"params";a:1:{i:1;s:7:"{count}";}}', 1);
+
+REPLACE INTO `sys_storage_mime_types` (`ext`, `mime_type`, `icon`, `icon_font`) VALUES
+('x-matroska', 'video/x-matroska', '', '');
+
+-- ACL
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'video conference', NULL, '_bx_messenger_acl_action_video_conference', '', 1, 0);
+SET @iIdActionEntryIMCreate = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'create talks', NULL, '_bx_messenger_acl_action_create_talks', '', 1, 0);
+SET @iIdActionEntryTalkCreate = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'send messages', NULL, '_bx_messenger_acl_action_send_messages', '', 1, 0);
+SET @iIdActionSendMessage = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'edit messages', NULL, '_bx_messenger_acl_action_edit_messages', '', 1, 0);
+SET @iIdActionEditMessages = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'delete messages', NULL, '_bx_messenger_acl_action_delete_messages', '', 1, 0);
+SET @iIdActionDeleteMessages = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'administrate talks', NULL, '_bx_messenger_acl_action_administrate_talks', '', 1, 0);
+SET @iIdActionAdminTalks = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'create broadcasts', NULL, '_bx_messenger_acl_action_create_broadcast', '', 1, 0);
+SET @iIdActionBroadcastTalks = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'create vc', NULL, '_bx_messenger_acl_action_create_vc', '', 1, 0);
+SET @iIdActionVCCreate = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'video recorder', NULL, '_bx_messenger_acl_action_video_recorder', '', 1, 0);
+SET @iIdActionVRecorder = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'join vc', NULL, '_bx_messenger_acl_action_join_vc', '', 1, 0);
+SET @iIdActionVCJoin = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'join personal vc', NULL, '_bx_messenger_acl_action_join_personal_vc', '', 1, 0);
+SET @iIdActionVCPJoin = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+(@sName, 'send files', NULL, '_bx_messenger_acl_action_send_files', '', 1, 0);
+SET @iIdActionSendFiles = LAST_INSERT_ID();
+
+SET @iUnauthenticated = 1;
+SET @iAccount = 2;
+SET @iStandard = 3;
+SET @iUnconfirmed = 4;
+SET @iPending = 5;
+SET @iSuspended = 6;
+SET @iModerator = 7;
+SET @iAdministrator = 8;
+SET @iPremium = 9;
+
+INSERT INTO `sys_acl_matrix` (`IDLevel`, `IDAction`) VALUES
+-- IM video conference
+(@iStandard, @iIdActionEntryIMCreate),
+(@iModerator, @iIdActionEntryIMCreate),
+(@iAdministrator, @iIdActionEntryIMCreate),
+(@iPremium, @iIdActionEntryIMCreate),
+-- JOIN talks' video conference
+(@iStandard, @iIdActionVCJoin),
+(@iModerator, @iIdActionVCJoin),
+(@iAdministrator, @iIdActionVCJoin),
+(@iPremium, @iIdActionVCJoin),
+-- JOIN personal video conference
+(@iStandard, @iIdActionVCPJoin),
+(@iModerator, @iIdActionVCPJoin),
+(@iAdministrator, @iIdActionVCPJoin),
+(@iPremium, @iIdActionVCPJoin),
+-- video conference
+(@iStandard, @iIdActionVCCreate),
+(@iModerator, @iIdActionVCCreate),
+(@iAdministrator, @iIdActionVCCreate),
+(@iPremium, @iIdActionVCCreate),
+-- video recorder
+(@iStandard, @iIdActionVRecorder),
+(@iModerator, @iIdActionVRecorder),
+(@iAdministrator, @iIdActionVRecorder),
+(@iPremium, @iIdActionVRecorder),
+-- create talk
+(@iStandard, @iIdActionEntryTalkCreate),
+(@iModerator, @iIdActionEntryTalkCreate),
+(@iAdministrator, @iIdActionEntryTalkCreate),
+(@iPremium, @iIdActionEntryTalkCreate),
+-- send messages
+(@iStandard, @iIdActionSendMessage),
+(@iModerator, @iIdActionSendMessage),
+(@iAdministrator, @iIdActionSendMessage),
+(@iPremium, @iIdActionSendMessage),
+-- administration messages edit/delete
+(@iModerator, @iIdActionEditMessages),
+(@iAdministrator, @iIdActionEditMessages),
+(@iModerator, @iIdActionDeleteMessages),
+(@iAdministrator, @iIdActionDeleteMessages),
+-- administration talks
+(@iModerator, @iIdActionAdminTalks),
+(@iAdministrator, @iIdActionAdminTalks),
+-- create broadcast talks
+(@iModerator, @iIdActionBroadcastTalks),
+(@iAdministrator, @iIdActionBroadcastTalks),
+-- send files
+(@iStandard, @iIdActionSendFiles),
+(@iModerator, @iIdActionSendFiles),
+(@iAdministrator, @iIdActionSendFiles),
+(@iPremium, @iIdActionSendFiles);
+
+-- MENU: Talk Menu
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+(CONCAT(@sName, '_lot_menu'), '_bx_messenger_menu_title_view', CONCAT(@sName, '_lot_menu'), @sName, 22, 0, 1, 'BxMessengerLotMenu', 'modules/boonex/messenger/classes/BxMessengerLotMenu.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES
+(CONCAT(@sName, '_lot_menu'), @sName, '_bx_messenger_menu_set_title_talk_menu', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`, `visibility_custom`) VALUES
+(CONCAT(@sName, '_lot_menu'), @sName, 'parent', '_bx_messenger_menu_item_title_parent_lot', '_bx_messenger_menu_item_title_parent_lot', 'javascript:void(0);', 'oMessenger.loadThreadsParent({id},''{type}'',{jot});', '_self', 'reply-all', '', 2147483647, 1, 0, 0, ''),
+(CONCAT(@sName, '_lot_menu'), @sName, 'list', '_bx_messenger_menu_item_title_group_list', '_bx_messenger_menu_item_title_group_list', 'javascript:void(0);', 'oMessenger.onSelectItem(this, {id});', '_self', 'bars', '', 2147483647, 1, 0, 1, 'a:3:{s:6:"module";s:12:"bx_messenger";s:6:"method";s:16:"is_block_version";s:6:"params";a:1:{i:0;s:2:"{}";}}'),
+(CONCAT(@sName, '_lot_menu'), @sName, 'video_call', '_bx_messenger_menu_item_title_video_call', '_bx_messenger_menu_item_title_video_call', 'javascript:void(0);', 'oMessenger.onStartVideoCall(this, {id}, ''{room}'');', '_self', 'video', '', 2147483647, 1, 0, 1, ''),
+(CONCAT(@sName, '_lot_menu'), @sName, 'star', '_bx_messenger_menu_item_title_star', '_bx_messenger_menu_item_title_star', 'javascript:void(0);', 'oMessenger.onStarLot(this, {id});', '_self', 'star', '', 2147483647, 1, 0, 2, ''),
+(CONCAT(@sName, '_lot_menu'), @sName, 'mute', '_bx_messenger_menu_item_title_mute', '_bx_messenger_menu_item_title_mute', 'javascript:void(0);', 'oMessenger.onMuteLot(this, {id});', '_self', 'bell', '', 2147483647, 1, 0, 3, ''),
+(CONCAT(@sName, '_lot_menu'), @sName, 'settings', '_bx_messenger_lots_menu_settings_title', '_bx_messenger_lots_menu_settings_title', 'javascript:void(0)', 'oMessenger.showInfoMenu(this, ''{lot_menu_id}'')', '_self', 'info-circle', '', 2147483647, 1, 0, 4, '');
+
+-- MENU: NAV MENU
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+(CONCAT(@sName, '_nav_menu'), '_bx_messenger_nav_menu_title_view', CONCAT(@sName, '_nav_menu'), @sName, 21, 0, 1, 'BxMessengerMainMenu', 'modules/boonex/messenger/classes/BxMessengerMainMenu.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES
+(CONCAT(@sName, '_nav_menu'), @sName, '_bx_messenger_nav_menu_title_view', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES
+(CONCAT(@sName, '_nav_menu'), @sName, 'inbox', '_bx_messenger_nav_menu_item_title_inbox', '_bx_messenger_nav_menu_item_title_inbox', 'javascript:void(0);', 'oMessenger.loadTalksList(this, {group : ''inbox''})', '_self', 'inbox col-red3', '', 2147483647, 1, 0, 0),
+(CONCAT(@sName, '_nav_menu'), @sName, 'direct', '_bx_messenger_nav_menu_item_title_direct', '_bx_messenger_nav_menu_item_title_direct', 'javascript:void(0);', 'oMessenger.loadTalksList(this, {group : ''direct''})', '_self', 'comment col-blue1', '', 2147483647, 1, 0, 1),
+(CONCAT(@sName, '_nav_menu'), @sName, 'threads', '_bx_messenger_nav_menu_item_title_threads', '_bx_messenger_nav_menu_item_title_threads', 'javascript:void(0);', 'oMessenger.loadTalksList(this, {group : ''threads''});', '_self', 'comments col-blue2', '', 2147483647, 1, 0, 2),
+(CONCAT(@sName, '_nav_menu'), @sName, 'reply', '_bx_messenger_nav_menu_item_title_replies', '_bx_messenger_nav_menu_item_title_replies', 'javascript:void(0);', 'oMessenger.loadTalksList(this, {group : ''replies''});', '_self', 'reply', '', 2147483647, 1, 0, 3),
+(CONCAT(@sName, '_nav_menu'), @sName, 'mentions_reactions', '_bx_messenger_nav_menu_item_title_mr', '_bx_messenger_nav_menu_item_title_mr', 'javascript:void(0)', 'oMessenger.loadTalksList(this, {group : ''mr''});', '_self', 'at col-green2', '', 2147483647, 1, 0, 4),
+(CONCAT(@sName, '_nav_menu'), @sName, 'saved', '_bx_messenger_nav_menu_item_title_saved', '_bx_messenger_nav_menu_item_title_saved', 'javascript:void(0);', 'oMessenger.loadTalksList(this, {group : ''saved''});', '_self', 'bookmark col-red1', '', 2147483647, 1, 0, 5),
+(CONCAT(@sName, '_nav_menu'), @sName, 'favorites', '_bx_messenger_nav_menu_item_title_favorites', '_bx_messenger_nav_menu_item_title_favorites', 'javascript:void(0);', '', '_self', 'star col-red2', '', 2147483647, 0, 0, 6);
+
+-- MENU: Groups Menu
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+(CONCAT(@sName, '_groups_menu'), '_bx_messenger_groups_menu_title_view', CONCAT(@sName, '_groups_menu'), @sName, 27, 0, 1, 'BxMessengerNavGroupsMenu', 'modules/boonex/messenger/classes/BxMessengerNavGroupsMenu.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES
+(CONCAT(@sName, '_groups_menu'), @sName, '_bx_messenger_groups_menu_title_view', 0);
+
+-- MENU: Talk Info Menu
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+(CONCAT(@sName, '_lot_info_menu'), '_bx_messenger_menu_lot_info_title', CONCAT(@sName, '_lot_info_menu'), @sName, 6, 0, 1, 'BxMessengerLotInfoMenu', 'modules/boonex/messenger/classes/BxMessengerLotInfoMenu.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES
+(CONCAT(@sName, '_lot_info_menu'), @sName, '_bx_messenger_menu_set_title_talk_info_menu', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`, `visibility_custom`) VALUES
+(CONCAT(@sName, '_lot_info_menu'), @sName, 'add_participants', '_bx_messenger_lots_menu_add_part', '_bx_messenger_lots_menu_add_part', 'javascript:void(0);', 'oMessenger.createList(''edit'');', '_self', 'plus-circle', '', 2147483647, 1, 0, 0, ''),
+(CONCAT(@sName, '_lot_info_menu'), @sName, 'delete', '_bx_messenger_lots_menu_delete', '_bx_messenger_lots_menu_delete', 'javascript:void(0);', 'oMessenger.onDeleteLot({id});', '_self', 'backspace', '', 2147483647, 1, 0, 1, ''),
+(CONCAT(@sName, '_lot_info_menu'), @sName, 'leave', '_bx_messenger_lots_menu_leave', '_bx_messenger_lots_menu_leave', 'javascript:void(0);', 'oMessenger.onLeaveLot({id});', '_self', 'sign-out-alt', '', 2147483647, 1, 0, 2, ''),
+(CONCAT(@sName, '_lot_info_menu'), @sName, 'media', '_bx_messenger_lots_menu_media', '_bx_messenger_lots_menu_media', 'javascript:void(0);', 'oMessenger.onMedia({id});', '_self', 'photo-video', '', 2147483647, 1, 0, 3, ''),
+(CONCAT(@sName, '_lot_info_menu'), @sName, 'clear', '_bx_messenger_lots_menu_clear', '_bx_messenger_lots_menu_clear', 'javascript:void(0);', 'oMessenger.onClearLot({id});', '_self', 'trash', '', 2147483647, 1, 0, 4, ''),
+(CONCAT(@sName, '_lot_info_menu'), @sName, 'settings', '_bx_messenger_lots_menu_settings', '_bx_messenger_lots_menu_settings', 'javascript:void(0)', 'oMessenger.onLotSettings(this, ''{lot_menu_id}'')', '_self', 'cogs', '', 2147483647, 1, 0, 5, ''),
+(CONCAT(@sName, '_lot_info_menu'), @sName, 'info', '_bx_messenger_lots_menu_info', '_bx_messenger_lots_menu_info', 'javascript:void(0)', 'oMessenger.onLotInfo(this, ''{lot_menu_id}'')', '_self', 'info-circle', '', 2147483647, 1, 0, 6, '');
+
+-- MENU: Message menu
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+(CONCAT(@sName, '_jot_menu'), '_bx_messenger_menu_jot_title', CONCAT(@sName, '_jot_menu'), @sName, 20, 0, 1, 'BxMessengerJotMenu', 'modules/boonex/messenger/classes/BxMessengerJotMenu.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES
+(CONCAT(@sName, '_jot_menu'), @sName, '_bx_messenger_menu_set_title_jot_menu', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`, `visibility_custom`) VALUES
+(CONCAT(@sName, '_jot_menu'), @sName, 'reaction', '_bx_messenger_jot_menu_reaction', '_bx_messenger_jot_menu_reaction', 'javascript:void(0);', 'oMessenger.onAddReaction(this);', '_self', 'smile', '', 2147483647, 1, 0, 0, ''),
+(CONCAT(@sName, '_jot_menu'), @sName, 'reply', '_bx_messenger_jot_menu_reply', '_bx_messenger_jot_menu_reply', 'javascript:void(0);', 'oMessenger.onReplyJot(this);', '_self', 'reply', '', 2147483647, 1, 0, 1, ''),
+(CONCAT(@sName, '_jot_menu'), @sName, 'share', '_bx_messenger_jot_menu_share', '_bx_messenger_jot_menu_share', 'javascript:void(0);', 'oMessenger.onCopyJotLink(this);', '_self', 'share', '', 2147483647, 1, 0, 2, ''),
+(CONCAT(@sName, '_jot_menu'), @sName, 'edit', '_bx_messenger_jot_menu_edit', '_bx_messenger_jot_menu_edit', 'javascript:void(0);', 'oMessenger.onEditJot(this);', '_self', 'edit', '', 2147483647, 1, 0, 3, ''),
+(CONCAT(@sName, '_jot_menu'), @sName, 'remove', '_bx_messenger_jot_menu_remove', '_bx_messenger_jot_menu_remove', 'javascript:void(0);', 'oMessenger.onDeleteJot(this);', '_self', 'backspace', '', 2147483647, 1, 0, 4, ''),
+(CONCAT(@sName, '_jot_menu'), @sName, 'thread', '_bx_messenger_jot_menu_thread', '_bx_messenger_jot_menu_thread', 'javascript:void(0);', 'oMessenger.onReplyInThread(this);', '_self', 'comment-dots', '', 2147483647, 1, 0, 5, ''),
+(CONCAT(@sName, '_jot_menu'), @sName, 'save', '_bx_messenger_jot_menu_save', '_bx_messenger_jot_menu_save', 'javascript:void(0);', 'oMessenger.onSaveJotItem(this);', '_self', 'bookmark', '', 2147483647, 1, 0, 6, '');
+
+-- MENU: CREATE CONVO
+INSERT INTO `sys_menu_templates` (`id`, `template`, `title`, `visible`) VALUES
+(ROUND(RAND()*(9999 - 1000) + 1000), 'menu-create-convo.html', '_bx_messenger_create_convo_template_title', 1);
+SET @iTemplId = (SELECT `id` FROM `sys_menu_templates` WHERE `template`='menu-create-convo.html' AND `title`='_bx_messenger_create_convo_template_title' LIMIT 1);
+
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+(CONCAT(@sName, '_create_convo_menu'), '_bx_messenger_create_convo_menu_title', CONCAT(@sName, '_create_convo_menu'), @sName, @iTemplId, 0, 0, 'BxMessengerCreateConvoMenu', 'modules/boonex/messenger/classes/BxMessengerCreateConvoMenu.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES
+(CONCAT(@sName, '_create_convo_menu'), @sName, '_bx_messenger_create_convo_menu_set_title', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES
+(CONCAT(@sName, '_create_convo_menu'), @sName, 'standard', '_bx_messenger_create_convo_menu_standard_system', '_bx_messenger_create_convo_menu_standard', 'javascript:void(0);', '{js_object}.onSelectConvoFilter();', '_self', 'users', '', 2147483647, 1, 0, 0),
+(CONCAT(@sName, '_create_convo_menu'), @sName, 'followers', '_bx_messenger_create_convo_menu_followers_system', '_bx_messenger_create_convo_menu_followers_system', 'javascript:void(0);', '{js_object}.onSelectConvoFilter(''followers'');', '_self', 'group', '', 2147483647, 1, 0, 1),
+(CONCAT(@sName, '_create_convo_menu'), @sName, 'friends', '_bx_messenger_create_convo_menu_friends_system', '_bx_messenger_create_convo_menu_friends_system', 'javascript:void(0);', '{js_object}.onSelectConvoFilter(''friends'');', '_self', 'user-friends', '', 2147483647, 1, 0, 1),
+(CONCAT(@sName, '_create_convo_menu'), @sName, 'broadcast', '_bx_messenger_create_convo_menu_broadcast_system', '_bx_messenger_create_convo_menu_broadcast_system', 'javascript:void(0);', '{js_object}.onSelectConvoFilter(''broadcast'');', '_self', 'bullhorn', '', 2147483647, 1, 0, 2);
+
+--- UPLOADERS
+INSERT INTO `sys_objects_uploader` (`object`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_messenger_html5', 1, 'BxMessengerUploaderHTML5', 'modules/boonex/messenger/classes/BxMessengerUploaderHTML5.php');
+
+-- MENU: custom menu for snippet meta info
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_messenger_profile_snippet_meta', '_bx_messenger_profile_title_snippet_meta', 'bx_messenger_profile_snippet_meta', @sName, 15, 0, 1, 'BxMessengerProfileMenuSnippetMeta', 'modules/boonex/messenger/classes/BxMessengerProfileMenuSnippetMeta.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES
+('bx_messenger_profile_snippet_meta', @sName, '_bx_messenger_set_title_profile_snippet_meta', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `editable`, `order`) VALUES
+('bx_messenger_profile_snippet_meta', @sName, 'message', '_bx_messenger_item_title_sm_message', '_bx_messenger_item_title_sm_message', 'page.php?i=messenger', '', '', 'comments', '', 2147483647, 0, 0, 1, 1);

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/js/emoji/emoji.min.js.map


+ 4585 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/js/messenger.js

@@ -0,0 +1,4585 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Messenger Messenger
+ * @ingroup    UnaModules
+ * @{
+ */
+
+/**
+ * Main messenger js file.
+ */
+
+; window.oMessenger = (function ($) {
+    let _oMessenger = null;
+
+    function oMessenger(oOptions) {
+        //list of selectors
+
+        this.sJitsiVideo = '#bx-messenger-jitsi-video';
+        this.sJitsiMain = '#bx-messenger-jitsi';
+        this.sEditJotArea = '.bx-messenger-edit-jot';
+        this.sEditJotAreaId = '#bx-messenger-edit-message-box';
+        this.sSendAreaActions = '.bx-messenger-post-box-send-actions';
+        this.sSendAreaActionsButtons = '.bx-messenger-post-box-send-actions-items';
+        this.sReactionsArea = '.bx-messenger-jot-reactions';
+        this.sFilesUploadAreaOnForm = '.bx-messenger-upload-area';
+        this.sTmpVideoFile = '.bx-messenger-attachment-temp-video';
+        this.sReactionItem = '.bx-messenger-reaction';
+        this.sReactionMenu = '.bx-messenger-reactions-menu';
+        this.sTalkAreaWrapper = '.bx-messenger-table-wrapper';
+        this.sActivePopup = '.bx-popup-applied:visible';
+        this.sJitsiButton = '#jitsi-button';
+        this.sJitsiJoinButton = '.bx-messenger-jots-message-vc-join-button';
+        this.sDateNavigator = '#date-time-navigator';
+        this.sUploaderAreaPrefix = 'bx-messenger-uploading-placeholder'; //
+        this.sJotMessageReply = '.bx-messenger-reply-area-message';
+        this.sJotMessageReplyArea = '.bx-messenger-reply-area';
+        this.sJotMessageTitle = '.bx-messenger-jots-title';
+        this.sTextArea = '.text-area';
+        this.sInboxTitle = '#bx-messenger-inbox-area-title';
+        this.sAttachFilesButton = 'a.attachefile, button.attachefile';
+        this.sTalksListSearchBar = '.bx-messenger-participants-usernames';
+
+        //global class options
+        this.oUsersTemplate = oOptions.templates && $(oOptions.templates['jot_template']);
+        this.sReactionTemplate = oOptions.templates && oOptions.templates['reaction_template'];
+        this.oActiveEditQuill = null;
+        this.sJotUrl = (oOptions && oOptions.jot_url) || sUrlRoot + 'm/messenger/archive/';
+        this.sInfoFavIcon = 'modules/boonex/messenger/template/images/icons/favicon-red-32x32.png';
+        this.sJotSpinner = '<img src="modules/boonex/messenger/template/images/icons/jot-loading.gif" />';
+        this.sDefaultFavIcon = $('link[rel="shortcut icon"]').attr('href');
+        this.iAttachmentUpdate = false;
+        this.iTimer = null;
+        this.sEmbedTemplate = (oOptions.templates && oOptions.templates['embed_template']) || '<a href="__url__">__url__</a>';
+        this.sThumbIcon = (oOptions.templates && oOptions.templates['thumb_icon']) || '';
+        this.sThumbLetter = (oOptions.templates && oOptions.templates['thumb_letter']) || '';
+        this.sAddUserTemplate = (oOptions.templates && oOptions.templates['add_user_item']) || '';
+        this.iMaxLength = (oOptions && oOptions.max) || 0;
+        this.iMaxReplyLength = 500;
+        this.iMaxHistory = oOptions.max_history_number || 50;
+        this.iStatus = document.hasFocus() ? 1 : 2; // 1- online, 2-away
+        this.iActionsButtonWidth = '2.25';
+        this.iScrollDownSpeed = 1500;
+        this.aJitisActiveUsers = {};
+        this.iHideUnreadBadge = 1000;
+        this.iRunSearchInterval = 500; // seconds
+        this.iMinHeightToStartLoading = 5; // scroll height to start history loading
+        this.iMinTimeBeforeToStartLoadingPrev = 300; // 300 millseconds before to start loading history
+        this.iUpdateProcessedMedia = 30000; //  30 seconds to check updated media files
+        this.iTypingUsersTitleHide = 1000; //hide typing users div when users stop typing
+        this.iLoadTimout = 0;
+        this.sFilterType = '';
+        this.iStarredTalks = false;
+        this.bActiveConnect = true;
+        this.aUsers = [];
+        this.iLastReadJotId = 0;
+        this.iReplyId = 0;
+        this.iThreadId = 0;
+        this.iScrollDownPositionJotId = 0; // position of the scroll for down arrow on history page when user jump to reply
+        this.oSendPool = new Map();
+
+        // files uploader
+        this.aUploaderQueue = Object.create(null);
+        this.sUploaderInputPrefix = 'messenger_uploader_';
+
+        // quill toolbar settings        
+        this.aToolbarSettings = oOptions.quill_toolbar;
+        this.lastEditText = '';
+        this.incomingMessage = 'modules/boonex/messenger/data/notify.mp3'; //beep file, occurs when message received
+        this.reaction = 'modules/boonex/messenger/data/reaction.mp3'; //beep file, occurs when message received
+        this.call = 'modules/boonex/messenger/data/call.mp3'; //incoming call file for video conferences
+        this.emojiObject = oOptions.emoji || null;
+        this.direction = oOptions.direction || 'LTR';
+
+        this.aPlatforms = ['MacIntel', 'MacPPC', 'Mac68K', 'Macintosh', 'iPhone', 'iPod', 'iPad', 'iPhone Simulator', 'iPod Simulator', 'iPad Simulator', 'Pike v7.6 release 92', 'Pike v7.8 release 517'];
+        this.oStorage = null;
+        this.oHistory = window.history || {};
+        this.oFilesUploader = null;
+        this.oActiveAudioInstance = null;
+        this.oActiveEmojiObject = Object.create(null);
+        this.sJitsiServerUrl = oOptions.jitsi_server;
+        this.aDatesItervals = [];
+        this.aSearchJotsList = null;
+        this.iScrollbarWidth = 0;
+        this.oEmojiTemplate = oOptions.templates && oOptions.templates['participants_item'];
+        this.aLoadingRequestsPool = []; // contains requests to load the talks when member clicks many talks with small delay and there is not enough time to load each talk
+
+        $(this).on('message', () => this.beep());
+
+        this.oSettings = {
+            url: oOptions.url || window.location.href,
+            title: document.title || '',
+            lot: oOptions.lot || 0,
+            name: (oOptions && oOptions.name) || 0,
+            user_id: (oOptions && oOptions.user_id) || 0,
+            group_id: (oOptions && oOptions.group_id) || 0,
+            area_type: 'inbox'
+        };
+
+
+        this.bCreateNew = false;
+        this.aTmpContent = [];
+        this.oPrevSettings = null;
+        this.bUniqueMode = !!oOptions['unique_mode'];
+        this.iSelectedJot = oOptions.jot_id || 0;
+        this.iLastUnreadJot = oOptions.last_unread_jot || 0;
+        this.iUnreadJotsNumber = oOptions.unread_jots || 0;
+        this.bAllowAttachMessages = oOptions.allow_attach || 0;
+        this.iLotType = oOptions.type || 0;
+        this.iMuted = +oOptions.muted;
+        this.bByUrl = +oOptions.by_url;
+        this.iSelectedPersonToTalk = oOptions.selected_profile || 0;
+        this.isBlockMessenger = typeof oOptions.block_version !== 'undefined' ? oOptions.block_version : $(window.oMessengerSelectors.JOT.lotsBlock).length === 0;
+
+        const { mainTalkBlock } = window.oMessengerSelectors.HISTORY;
+        this.oFilesUploaderSettings = typeof oOptions['files_uploader'] !== 'undefined' ? $.extend(oOptions['files_uploader'], { main_object_name: mainTalkBlock }) : null;
+
+        // Real-time WebSockets framework class
+        this.oRTWSF = (oOptions && oOptions.oRTWSF) || window.oRTWSF || null;
+
+        // Main Messenger menu
+        this.oMenu = null;
+
+        // text editor
+        this.quill = null;
+
+        $(window).on('popstate', (e) => this.updateHistory(e));
+
+        this.oNotifications = Object.assign({
+            'inbox': 0,
+            'threads': Object.create({}),
+            'broadcast': Object.create({}),
+            'direct': Object.create({}),
+            'groups': Object.create({})
+        }, oOptions.messages || {});
+    }
+
+    oMessenger.prototype.updateHistory = function (e) {
+        const _this = this,
+            { state } = this.oHistory,
+            { lot, jot, action, type, area, menu } = state || {};
+
+
+        if (this.oEditor)
+            this.oEditor.blur();
+
+        if (oMUtils.isMobile() && typeof _this.oMenu !== 'undefined') {
+            if (_this.oMenu.isHistoryColActive())
+                return _this.oMenu.showHistoryPanel();
+
+            if (_this.oMenu.isMenuColActive())
+                return _this.oMenu.toggleMenuPanel();
+        }
+
+        switch (action) {
+            case 'init':
+            case 'load_talk':
+                if (lot) {
+                    _this.oMenu.toggleMenuItem(undefined, menu);
+                    if (area !== _this.oSettings.area_type && !_this.isBlockVersion()) {
+                        _this.loadTalksListByParam({ group: area }, () => {
+                            _this.loadTalk(lot, jot);
+                        });
+                    } else
+                        _this.loadTalk(lot);
+                }
+                break;
+            case 'create_list':
+                _this.createList(type);
+                break;
+        }
+    }
+
+    oMessenger.prototype.updateCreateButton = function () {
+        const { area_type } = this.oSettings,
+            { createTalkButton, talksList, topItem } = window.oMessengerSelectors.TALKS_LIST;
+
+        if (['direct', 'groups'].includes(area_type))
+            $(createTalkButton).show();
+        else
+            $(createTalkButton).hide();
+
+        $(talksList).find(topItem).addClass('hidden');
+    }
+
+    oMessenger.prototype.playSound = function (sFile, bRepeat = false) {
+        if (!_oMessenger[sFile])
+            return;
+
+        try {
+            if (bRepeat)
+                this.oActiveAudioInstance = createjs.Sound.play(sFile, {
+                    interrupt: createjs.Sound.INTERRUPT_ANY,
+                    loop: -1
+                });
+            else
+                createjs.Sound.play(sFile, {});
+        } catch (e) {
+            console.log('Sound is not supported in your browser');
+        }
+    };
+
+    /**
+     * Init current chat/talk/lot settings
+     */
+
+    oMessenger.prototype.initLotSettings = function (oOptions) {
+        const _this = this,
+            { lot, jot, last_unread_jot, unread_jots, title, group_id } = oOptions || {},
+            { conversationBody } = window.oMessengerSelectors.HISTORY;
+
+        for (const key in this.oSettings) {
+            if (typeof oOptions[key] !== 'undefined')
+                _this.oSettings[key] = oOptions[key];
+        }
+
+        this.iSelectedJot = jot || 0;
+        this.iLastUnreadJot = last_unread_jot || 0;
+        this.iUnreadJotsNumber = unread_jots || 0;
+        this.iLastReadJotId = 0;
+
+        this.updateCounters(unread_jots, true);
+
+        /* runs periodic to find not processed videos in chat history and replace them with processed videos */
+        setInterval(
+            function () {
+                _this.updateProcessedMedia();
+            }, _this.iUpdateProcessedMedia
+        );
+
+        $(document).ready(() => $(conversationBody).initJotIcons().initAccordion());
+
+        this.blockSendMessages(!this.oRTWSF || !this.oRTWSF.isInitialized() || (!lot && !this.iSelectedPersonToTalk && this.iLotType === 2));
+    };
+
+    oMessenger.prototype.setScrollBarWidth = function () {
+        const { talkBlock, mainScrollArea, scrollAreaItem } = window.oMessengerSelectors.HISTORY;
+        this.iScrollbarWidth = $(talkBlock).prop('offsetWidth') - $(talkBlock).prop('clientWidth');
+
+        if (!$(talkBlock).prop('clientWidth'))
+            return;
+
+        // set dates intervals top div width without scrollbar width
+        $(this.sDateNavigator).width($(talkBlock).prop('clientWidth'));
+
+        if (this.iScrollbarWidth)
+            $(`${mainScrollArea}, ${scrollAreaItem}`).css('right', `calc( ${this.iScrollbarWidth}px + 0.5rem )`);
+    }
+
+    oMessenger.prototype.initScrollArea = function () {
+        const _this = this,
+            { talkBlock, mainScrollArea, unreadJotsCounter } = window.oMessengerSelectors.HISTORY,
+            { talkListJotSelector } = window.oMessengerSelectors.JOT;
+
+        let iBottomScreenPos = $(talkBlock).scrollTop() + $(talkBlock).innerHeight();
+        let bStartLoading = !(_this.iLastUnreadJot || _this.iSelectedJot);
+        let iCounterValue = +$(unreadJotsCounter).text();
+        let iUpdateCounter = null;
+        let iPrevScrollHeight = null;
+
+        $(talkBlock).scroll(function (e) {
+            const isScrollAvail = $(this).prop('scrollHeight') > $(this).prop('clientHeight'),
+                iScrollHeight = $(this).prop('scrollHeight') - $(this).prop('clientHeight'),
+                iScrollPosition = $(this).prop('scrollHeight') - $(this).prop('clientHeight') - $(this).scrollTop(),
+                iPassPixelsToStart = isScrollAvail ? _this.iMinHeightToStartLoading / 100 * $(this).prop('clientHeight') * iScrollHeight / $(this).prop('clientHeight') : 0,
+                isTopPosition = ($(this).scrollTop() <= iPassPixelsToStart) || ($(this).scrollTop() === 0),
+                isBottomPosition = $(this).scrollTop() > ($(this).prop('scrollHeight') - $(this).prop('clientHeight') - iPassPixelsToStart);
+
+            if (!isScrollAvail)
+                return;
+
+            if (iPrevScrollHeight === null) {
+                $(this).trigger(jQuery.Event('scrollMount'));
+                iPrevScrollHeight = $(this).prop('scrollHeight');
+            } else if (iPrevScrollHeight !== $(this).prop('scrollHeight')) {
+                $(this).trigger(jQuery.Event('scrollHeightUpdated'));
+                iPrevScrollHeight = $(this).prop('scrollHeight');
+            }
+
+            iBottomScreenPos = $(talkBlock).scrollTop() + $(talkBlock).innerHeight();
+            iCounterValue = +$(unreadJotsCounter).text();
+            if (_this.iLastUnreadJot) {
+                $(talkListJotSelector).each(function () {
+                    const iId = +$(this).data('id');
+                    const { top } = $(this).position();
+                    const iJotBottomPos = top + $(this).innerHeight();
+
+                    if (iId < _this.iLastUnreadJot)
+                        return;
+
+                    if (iId > _this.iLastUnreadJot && iJotBottomPos <= iBottomScreenPos) {
+                        _this.iLastReadJotId = iId;
+                        $(unreadJotsCounter).text(--iCounterValue);
+                        if (!iCounterValue)
+                            $(unreadJotsCounter).hide();
+
+                        clearTimeout(iUpdateCounter);
+                    }
+                });
+
+                if (_this.iLastReadJotId && _this.iLastUnreadJot !== _this.iLastReadJotId) {
+                    _this.iLastUnreadJot = _this.iLastReadJotId;
+                    iUpdateCounter = setTimeout(() => {
+                        if (document.hasFocus()) {
+                            _this.broadcastView(_this.iLastUnreadJot);
+                            _this.updateCounters(iCounterValue);
+                        }
+                    }, 1000);
+                }
+            }
+
+            if (!isBottomPosition && !isTopPosition && bStartLoading)
+                bStartLoading = false;
+
+            if (iScrollPosition > $(talkListJotSelector).last().height() || iCounterValue)
+                $(mainScrollArea).fadeIn();
+            else
+                $(mainScrollArea).fadeOut();
+
+            if ((isBottomPosition || isTopPosition)) {
+                if (!bStartLoading) {
+                    bStartLoading = true;
+                    _this.iLoadTimout = setTimeout(
+                        function () {
+
+                            e.stopPropagation();
+                            e.preventDefault();
+
+                            _this.updateJots({
+                                action: isTopPosition ? 'prev' : 'new',
+                                position: isBottomPosition ? 'freeze' : 'position',
+                                last_viewed_jot: _this.iLastUnreadJot
+                            });
+
+                        }, _this.iMinTimeBeforeToStartLoadingPrev);
+                }
+            } else
+                clearTimeout(_this.iLoadTimout);
+        });
+    }
+
+    oMessenger.prototype.loadTalkFiles = function (oBlock, iCount, fCallback) {
+        const { msgContainer } = window.oMessengerSelectors.SYSTEM;
+        bx_loading(oBlock, true);
+        $.get('modules/?r=messenger/get_talk_files/', {
+            number: iCount,
+            lot_id: this.oSettings.lot
+        }, function ({ code, html, total }) {
+            bx_loading(oBlock, false);
+
+            if (!code && !($(msgContainer, oBlock).length && $(html).hasClass(msgContainer)))
+                $(oBlock)
+                    .append(html)
+                    .bxMsgTime();
+
+            if (typeof (fCallback) === 'function')
+                fCallback({ total });
+        }, 'json');
+    };
+
+    oMessenger.prototype.loadTalkInfo = function (oBlock, fCallback) {
+        const { msgContainer } = window.oMessengerSelectors.SYSTEM;
+        bx_loading(oBlock, true);
+        $.get('modules/?r=messenger/get_talk_info/', {
+            lot_id: this.oSettings.lot
+        }, function ({ code, html, total }) {
+            bx_loading(oBlock, false);
+
+            if (!code && !($(msgContainer, oBlock).length && $(html).hasClass(msgContainer)))
+                $(oBlock)
+                    .append(html)
+                    .bxMsgTime();
+
+            if (typeof (fCallback) === 'function')
+                fCallback({ total });
+        }, 'json');
+    };
+
+    oMessenger.prototype.updateLotSettings = function (oOptions) {
+        this.initLotSettings(oOptions);
+    }
+
+    oMessenger.prototype.initTextEditor = function (oSettings) {
+        return new oMessengerEditor(Object.assign({}, oSettings, {
+            quill_toolbar: this.aToolbarSettings,
+            showToolbar: () => !oMUtils.isMobile()
+        }));
+    }
+
+    oMessenger.prototype.createFilesUploader = function () {
+        const _this = this,
+            { attachmentArea } = window.oMessengerSelectors.TEXT_AREA;
+
+        let iTime = (new Date()).getTime();
+        const InputName = `${_this.sUploaderInputPrefix}${iTime}`;
+        const sInput = `<input type="file" name="${InputName}" required style="display:none;"/>`;
+
+        $(attachmentArea).before(sInput);
+
+        if (!this.oFilesUploader)
+            this.initFilesUploader();
+
+        this.oFilesUploader.create(InputName);
+    }
+
+    oMessenger.prototype.initFilesUploader = function (sSelector = '') {
+        const { attachmentArea } = window.oMessengerSelectors.TEXT_AREA;
+        const { giphyMain } = window.oMessengerSelectors.GIPHY;
+
+        if (!this.oFilesUploaderSettings) return;
+
+        this.oFilesUploader = new oMessengerUploader({
+            ...this.oFilesUploaderSettings,
+
+            onAddFilesCallback: () => {
+                const $area = $(`${sSelector}${attachmentArea}`);
+                if ($area.children().length) {
+                    $(`${sSelector}${giphyMain}`).fadeOut();
+                    $area.empty();
+                }
+            },
+
+            onUpdateAttachments: (bUpdate) => this.updateSendArea(bUpdate),
+
+            onUploadingComplete: (sName, aFiles, fCallback) => {
+                const oQueue = this.aUploaderQueue[sName];
+                if (!oQueue || !aFiles.length) return false;
+
+                const { id, lot } = oQueue;
+                const fDispatchFilesUploaded = () => {
+                    const iJotId = +(this.aUploaderQueue[sName]?.uploading_jot_id || 0);
+                    const detail = { jot_id: iJotId, lot, files: aFiles };
+                    if (typeof window.CustomEvent === 'function') {
+                        document.dispatchEvent(new CustomEvent('bxMessengerFilesUploaded', { detail }));
+                        return;
+                    }
+
+                    const evt = document.createEvent('CustomEvent');
+                    evt.initCustomEvent('bxMessengerFilesUploaded', true, true, detail);
+                    document.dispatchEvent(evt);
+                };
+
+                const fUpload = (iJotId) => $.post('modules/?r=messenger/update_uploaded_files/', {
+                    jot_id: iJotId,
+                    files: aFiles
+                }, ({ code, message }) => {
+                    if (+code && message) {
+                        bx_alert(message);
+                    } else {
+                        this.broadcastMessage({ jot_id: iJotId, lot, addon: 'update_attachment' });
+
+                        const $uploaderArea = $(`#${this.sUploaderAreaPrefix}-${id}`);
+                        if ($uploaderArea.length) {
+                            this.attacheFiles(iJotId, true, () =>
+                                $uploaderArea.fadeOut(() => {
+                                    $uploaderArea.remove();
+                                    fDispatchFilesUploaded();
+                                })
+                            );
+                        } else {
+                            fDispatchFilesUploaded();
+                        }
+                    }
+
+                    if (typeof fCallback === 'function') fCallback();
+                    delete this.aUploaderQueue[sName];
+                }, 'json');
+
+                const oThread = this.oSendPool.get(id);
+                const fUploadWhenReady = () => {
+                    const iJotId = +(this.aUploaderQueue[sName]?.uploading_jot_id || 0);
+                    if (iJotId) {
+                        return fUpload(iJotId);
+                    }
+
+                    if (oThread?.promise) {
+                        oThread.promise.then(() => {
+                            const iReadyJotId = +(this.aUploaderQueue[sName]?.uploading_jot_id || 0);
+                            if (iReadyJotId)
+                                fUpload(iReadyJotId);
+                            else {
+                                if (typeof fCallback === 'function') fCallback();
+                                delete this.aUploaderQueue[sName];
+                            }
+                        });
+                        return;
+                    }
+
+                    let iAttempts = 0;
+                    const iMaxAttempts = 5;
+                    const iDelayMs = 500;
+                    const iWaiter = setInterval(() => {
+                        iAttempts++;
+                        const iPendingJotId = +(this.aUploaderQueue[sName]?.uploading_jot_id || 0);
+                        const oPendingThread = this.oSendPool.get(id);
+                        if (iPendingJotId) {
+                            clearInterval(iWaiter);
+                            fUpload(iPendingJotId);
+                            return;
+                        }
+
+                        if (oPendingThread?.promise) {
+                            clearInterval(iWaiter);
+                            oPendingThread.promise.then(() => {
+                                const iResolvedJotId = +(this.aUploaderQueue[sName]?.uploading_jot_id || 0);
+                                if (iResolvedJotId)
+                                    fUpload(iResolvedJotId);
+                                else {
+                                    if (typeof fCallback === 'function') fCallback();
+                                    delete this.aUploaderQueue[sName];
+                                }
+                            });
+                            return;
+                        }
+
+                        if (iAttempts >= iMaxAttempts) {
+                            clearInterval(iWaiter);
+                            if (typeof fCallback === 'function') fCallback();
+                            delete this.aUploaderQueue[sName];
+                        }
+                    }, iDelayMs);
+                };
+
+                fUploadWhenReady();
+            }
+        }).getUploader();
+
+        return this.oFilesUploader;
+    };
+
+    oMessenger.prototype.initTextArea = function (fCallback, sTextAreaSelector = null) {
+        const _this = this,
+            { inputArea, sendButton, attachmentGroup } = window.oMessengerSelectors.TEXT_AREA,
+            { talkListJotSelector } = window.oMessengerSelectors.JOT;
+
+        // init files uploader
+        _this.createFilesUploader();
+
+        this.oEditor = this.initTextEditor({
+            files_uploader: _this.oFilesUploader,
+            selector: sTextAreaSelector || inputArea,
+            placeholder: _t('_bx_messenger_post_area_message'),
+            onEnter: () => {
+                if (!(oMUtils.isMobileDevice() || oMUtils.isUnaMobileApp())) {
+                    $(sendButton).click();
+                    return true;
+                }
+
+                _this.oEditor.focus();
+                return false;
+            },
+            onInit: function () {
+                if (typeof fCallback === 'function') {
+                    fCallback();
+                }
+            },
+            onUp: () => {
+                if ($(talkListJotSelector).length && _this.oEditor.length <= 1) {
+                    const oJot = $(`${talkListJotSelector}`).last();
+                    if (+oJot.data('my'))
+                        _this.editJot(oJot);
+                    return false;
+                }
+                return true;
+            },
+            onChange: () => {
+                const { ops } = _this.oEditor.getContents(),
+                    { lot, name, user_id } = _this.oSettings;
+
+                _this.updateSendAreaHeight();
+                if (!lot || _this.bCreateNew)
+                    return;
+
+                if (_this.oEditor.length > 1) {
+                    const oJotInfo = Object.create(null);
+                    oJotInfo.message = ops;
+                    if (_this.iReplyId)
+                        oJotInfo.reply = _this.iReplyId;
+
+                    _this.oStorage.saveLot(lot, oJotInfo);
+                } else
+                    _this.oStorage.deleteLot(lot);
+
+                // show typing area when member post the message
+                if (_this.oRTWSF)
+                    _this.oRTWSF.typing({ lot, name, user_id });
+            }
+        });
+
+        // when member clicks on send message icon
+        $(sendButton).on('click', () => {
+            if (_this.sendMessage(_this.oEditor.length === 1 ? '' : _this.oEditor.html(), undefined, () => {
+                if (_this.oFilesUploader)
+                    _this.oFilesUploader.clean();
+
+                _this.oPrevSettings = null;
+                _this.aTmpContent = [];
+                _this.oStorage.deleteLot(_this.oSettings.lot);
+            })) {
+                _this.aTmpContent = _this.oEditor.getContents();
+                _this.oEditor.setContents([]);
+                if (!(oMUtils.isMobileDevice() || oMUtils.isUnaMobileApp()))
+                    _this.oEditor.focus();
+                else
+                    _this.oEditor.blur();
+            }
+        });
+
+        _this.updateSendAreaButtons();
+
+        oMessengerEmoji.init($.extend(_this.emojiObject, {
+            onEmojiSelect: function (oEmoji) {
+                if (_this.oActiveEmojiObject['type'] === 'reaction' && +_this.oActiveEmojiObject['param'])
+                    _this.onJotAddReaction(oEmoji, +_this.oActiveEmojiObject['param']);
+                else if (_this.oActiveEmojiObject['type'] === 'textarea')
+                    _this.onTextAreaAddEmoji(oEmoji);
+
+                _this.oActiveEmojiObject = Object.create(null);
+            }
+        }));
+
+        $(_this.sSendAreaActionsButtons)
+            .find(window.oMessengerSelectors.EMOJI.sendEmojiButton)
+            .attacheEmoji((e) => {
+                const { textArea } = window.oMessengerSelectors.TEXT_AREA;
+
+                if (window.oMessengerEmoji)
+                    window.oMessengerEmoji.emojiCall({
+                        position: 'absolute',
+                        left: '2.5rem',
+                        right: '',
+                        top: '',
+                        bottom: $(textArea).height()
+                    }, () => _this.oActiveEmojiObject['type'] = 'textarea');
+            });
+
+        $(_this.sAttachFilesButton).on('click', () => {
+            if (!$(`${attachmentGroup} [name^="${_this.sUploaderInputPrefix}"]`).length || !_this.oFilesUploader)
+                _this.createFilesUploader();
+
+            _this.oFilesUploader.browse();
+        });
+
+        // enable video recorder if it is not IOS/Mac devices
+        if (!_this.aPlatforms.includes(navigator.platform))
+            $(_this.sSendAreaActionsButtons)
+                .find('li.video').show();
+
+        /* Giphy Integration */
+        const oGiphy = new oMessengerGiphy();
+        oGiphy.initGiphy();
+
+        _this.setStorageData();
+        _this.updateSendAreaHeight();
+        _this.checkNotFinishedTalks();
+    }
+
+    oMessenger.prototype.setStorageData = function () {
+        const _this = this,
+            { sendButton } = window.oMessengerSelectors.TEXT_AREA,
+            { jotMain } = window.oMessengerSelectors.JOT;
+
+        // check if the not finished message for the talk exists
+        const mixedStorageMessage = _this.oStorage.getLot(_this.oSettings.lot);
+        if (typeof mixedStorageMessage === 'object') {
+            const { message, reply } = mixedStorageMessage;
+
+            if (Array.isArray(mixedStorageMessage) && mixedStorageMessage.length) {
+                _this.oEditor.setContents(mixedStorageMessage);
+                $(sendButton).fadeIn();
+            } else if (Array.isArray(message) && message.length) {
+                _this.oEditor.setContents(message);
+                $(sendButton).fadeIn();
+            } else if (typeof mixedStorageMessage === 'string' && mixedStorageMessage.length)
+                _this.oEditor.setText(mixedStorageMessage);
+
+            if (+reply) {
+                _this.iReplyId = +reply;
+                _this.replyJot($(`${jotMain}[data-id="${_this.iReplyId}"]`), _this.iReplyId);
+            } else
+                _this.iReplyId = 0;
+        } else
+            _this.iReplyId = 0;
+    }
+
+    oMessenger.prototype.updateSendAreaHeight = function () {
+        const { inputArea } = window.oMessengerSelectors.TEXT_AREA,
+            oInputArea = $(inputArea),
+            fMaxHeight = parseInt(oInputArea.css('max-height'));
+
+        if (oInputArea.outerHeight() >= fMaxHeight)
+            oInputArea.css('overflow-y', 'auto');
+        else
+            oInputArea.css('overflow-y', 'visible');
+    }
+
+    oMessenger.prototype.updateSendArea = function (bFilesEmpty) {
+        const { attachmentGroup } = window.oMessengerSelectors.TEXT_AREA;
+
+        if (bFilesEmpty)
+            $(attachmentGroup).hide();
+        else
+            $(attachmentGroup).show();
+    };
+
+    oMessenger.prototype.onOuterClick = function (oEvent) {
+        const { inputArea } = window.oMessengerSelectors.TEXT_AREA,
+            { giphySendArea, giphyMain } = window.oMessengerSelectors.GIPHY;
+        if ($(oEvent.target).closest(inputArea).length)
+            return;
+
+        if (!$(oEvent.target).closest(giphySendArea).length && !$(oEvent.target).closest(giphyMain).length)
+            $(giphyMain).fadeOut();
+    };
+
+    /**
+     * Mark talks in case if they are contains not finiched messages
+     */
+
+    oMessenger.prototype.checkNotFinishedTalks = function () {
+        const { talkItem } = window.oMessengerSelectors.TALKS_LIST,
+            oLots = this.oStorage.getLots(),
+            oLotsKeys = (oLots && Object.keys(oLots)) || [];
+
+        $(`${talkItem} .info`).each(function () {
+            $(this).html('');
+        });
+
+        if (oLotsKeys.length)
+            oLotsKeys.map((iLot) => $(`${talkItem}[data-lot="${iLot}"] .info`).html(oLots[iLot].length ? '<i class="sys-icon pen"></i>' : ''));
+    };
+
+    /**
+     * Init send message area buttons
+     */
+
+    oMessenger.prototype.updateSendAreaButtons = function (sSelector) {
+        const _this = this,
+            oSmile = $(`${sSelector}${_this.sSendAreaActionsButtons}`)
+                .find('button.smiles')
+                .parent();
+
+        if (oMUtils.isMobile())
+            oSmile.hide();
+        else
+            oSmile.show(100);
+    };
+
+    oMessenger.prototype.blockSendMessages = function (bBlock) {
+        const { textArea, textAreaDisabled } = window.oMessengerSelectors.TEXT_AREA;
+
+        if (bBlock !== undefined)
+            this.bActiveConnect = !bBlock;
+
+        if (!this.bActiveConnect && !$(textArea).hasClass(textAreaDisabled))
+            $(textArea).addClass(textAreaDisabled);
+
+        if (this.bActiveConnect)
+            $(textArea).removeClass(textAreaDisabled);
+    }
+
+    oMessenger.prototype.isBlockVersion = function () {
+        return this.isBlockMessenger;
+    }
+
+    oMessenger.prototype.showWelcomeMessage = function (sMessage) {
+        if (sMessage.length)
+            bx_alert(sMessage);
+    }
+
+    oMessenger.prototype.removeEditArea = function (oEvent) {
+        const _this = this,
+            bEditArea = $(oEvent.target).parents(_this.sEditJotArea).length;
+
+        $(this.sEditJotArea).each(function () {
+            if (!bEditArea)
+                _this.cancelEdit($(this));
+        });
+    };
+
+    /**
+     * Update status of the member
+     *@param object oData changed profile's settings
+     */
+    oMessenger.prototype.updateStatuses = function (oData) {
+        let sClass = 'offline';
+
+        switch (oData.status) {
+            case 1:
+                sClass = 'online';
+                break;
+            case 2:
+                sClass = 'away';
+                break;
+            default:
+                sClass = 'offline';
+        }
+
+        $('div[data-user-status="' + oData.user_id + '"]')
+            .removeClass('online offline away')
+            .addClass(sClass)
+            .attr('title', _t('_bx_messenger_' + sClass));
+    }
+
+    /**
+     * Set users statuses
+     *@param object oObject from which to copy status to all other places where status exists (to make all statuses similar)
+     */
+    oMessenger.prototype.setUsersStatuses = function (oObject) {
+        const { talkStatus } = window.oMessengerSelectors.TALKS_LIST,
+            iUser = oObject
+                .find(talkStatus)
+                .data('user-status');
+
+        if (parseInt(iUser)) {
+            const classList = oObject
+                .find(talkStatus)
+                .attr('class')
+                .split(/\s+/);
+
+            if (typeof classList[2] !== 'undefined') {
+                $('div[data-user-status="' + iUser + '"]').removeClass('online offline away').addClass(classList[2]).attr('title', _t('_bx_messenger_' + classList[2]));
+            }
+        }
+    };
+
+    /**
+     * Load logged member's message template
+     */
+    oMessenger.prototype.loadMembersTemplate = function () {
+        const _this = this;
+
+        if (_this.oUsersTemplate == null)
+            $.get('modules/?r=messenger/load_members_template',
+                function ({ data }) {
+                    if (typeof data !== 'undefined' && data.length) {
+                        _this.oUsersTemplate = $(data);
+                    }
+                }, 'json');
+    }
+
+    /**
+     * Search talks by text criteria and current selected area
+     *@param function type callback function
+     */
+    oMessenger.prototype.searchByItems = function (fCallback) {
+        const _this = this,
+            { group_id } = _this.oSettings,
+            { talksList, searchCloseIcon, searchCriteria } = window.oMessengerSelectors.TALKS_LIST,
+            sText = $(searchCriteria).val(),
+            searchFunction = () => {
+                _this.iTimer = setTimeout(() => bx_loading($(talksList), true), 1000);
+                $.get('modules/?r=messenger/search', {
+                    param: sText || '',
+                    type: _this.sFilterType,
+                    starred: +_this.iStarredTalks,
+                    group_id
+                },
+                    function ({ code, html, search_list }) {
+                        clearTimeout(_this.iTimer);
+
+                        bx_loading($(talksList), false);
+                        if (parseInt(code) === 1)
+                            window.location.reload();
+                        else {
+                            _this.aSearchJotsList = typeof search_list !== 'undefined' ? search_list : null;
+                            _this.showSearchCounter(_this.oSettings.lot);
+
+                            const fFuncCallback = () => typeof fCallback === 'function' && fCallback();
+                            if (!parseInt(code)) {
+                                $(' > ul', talksList)
+                                    .html(html)
+                                    .bxMsgTime()
+                                    .fadeIn(fFuncCallback);
+
+                                $(talksList).initLazyLoading((oObject, bFlag) => _this.loadTalksList(oObject, bFlag));
+                            } else
+                                fFuncCallback();
+                        }
+
+                        if (sText && sText.length)
+                            $(searchCloseIcon).show();
+                        else
+                            $(searchCloseIcon).hide();
+
+                    }, 'json');
+            };
+        clearTimeout(_this.iTimer);
+        this.iTimer = setTimeout(searchFunction, _this.iRunSearchInterval);
+    }
+
+    oMessenger.prototype.disableCreateList = function () {
+        const { conversationBlockHistory, createTalkArea } = window.oMessengerSelectors.HISTORY,
+            { talkTitle } = window.oMessengerSelectors.TEXT_AREA;
+
+        $(createTalkArea, conversationBlockHistory).hide().remove();
+        $(talkTitle).remove();
+
+        this.bCreateNew = false;
+        this.oMenu.toggleAlwaysOnTop(false);
+    }
+
+    oMessenger.prototype.createList = function (action, fCallback) {
+        const { lot, area_type, group_id } = this.oSettings,
+            { panel } = window.oMessengerSelectors.TALKS_LIST,
+            { textArea } = window.oMessengerSelectors.TEXT_AREA,
+            {
+                mainTalkBlock,
+                tableWrapper,
+                conversationBlockHistory,
+                createTalkArea,
+                talkBlockWrapper,
+                historyColumn
+            } = window.oMessengerSelectors.HISTORY,
+            _this = this;
+
+        const bIsPanel = $(historyColumn).hasClass(panel);
+        if (bIsPanel && $(createTalkArea, conversationBlockHistory).length)
+            return;
+
+        if (oMUtils.isMobile()) {
+            if (!_this.oMenu.isHistoryColActive())
+                _this.oMenu.showHistoryPanel();
+        }
+
+        _this.oMenu.toggleAlwaysOnTop();
+
+        bx_loading($(mainTalkBlock), true);
+        $.post('modules/?r=messenger/load_list', { group_id, area_type, action, lot }, function ({
+            code,
+            content,
+            text_area,
+            msg
+        }) {
+            bx_loading($(mainTalkBlock), false);
+            if (+code && msg) {
+                bx_alert(msg);
+                _this.oMenu.toggleAlwaysOnTop();
+                return;
+            }
+
+            if (!parseInt(code)) {
+                // flag to detect if the new conversation ability is available to create the talk
+                _this.bCreateNew = action === 'new';
+
+                $(talkBlockWrapper, conversationBlockHistory)
+                    .before(content);
+
+                if (text_area) {
+                    if ($(textArea, mainTalkBlock).length)
+                        $(textArea, mainTalkBlock).replaceWith(text_area);
+                    else
+                        $(tableWrapper).append(text_area);
+
+                    if (_this.oFilesUploader)
+                        _this.oFilesUploader.removeCurrent();
+
+                    _this.initTextArea();
+                }
+
+                if (typeof fCallback == 'function')
+                    fCallback();
+            }
+        }, 'json');
+    }
+
+    oMessenger.prototype.saveParticipantsList = function (iLotId) {
+        const _this = this,
+            { createTalkArea, mainTalkBlock } = window.oMessengerSelectors.HISTORY,
+            { blockContainer, bxTitle, blockMenu } = window.oMessengerSelectors.SYSTEM;
+
+        let _iLotId = iLotId;
+        $.post('modules/?r=messenger/save_lots_parts', {
+            lot: _iLotId,
+            participants: _this.getParticipantsList(),
+            is_block: _this.isBlockVersion()
+        },
+            function ({ code, message, lot, header, buttons }) {
+                if (+code === 1)
+                    bx_alert(message);
+                else {
+                    _this.disableCreateList();
+
+                    if (typeof header !== 'undefined') {
+                        $(mainTalkBlock)
+                            .closest(blockContainer)
+                            .find(bxTitle)
+                            .html(header)
+                            .siblings(blockMenu)
+                            .html(buttons)
+                            .fadeIn();
+                    }
+
+                    if (lot) {
+                        _this.oSettings.lot = lot;
+                        _this.blockSendMessages(false);
+
+                        if (!_this.isBlockVersion())
+                            _this.upLotsPosition(_this.oSettings);
+
+                        if (!_iLotId)
+                            _this.updateTalksListArea();
+                    }
+
+                    if (_this.oEditor)
+                        setTimeout(() => _this.oEditor.focus(), 1000);
+                }
+
+            }, 'json');
+    };
+
+    oMessenger.prototype.updateTalksListArea = function () {
+        const { talksList } = window.oMessengerSelectors.TALKS_LIST,
+            { msgContainer } = window.oMessengerSelectors.SYSTEM;
+
+        if ($(msgContainer, talksList).length)
+            $(msgContainer, talksList).remove();
+    }
+
+    oMessenger.prototype.updateCommentsAreaWidth = function (fWidth) {
+        const { recorderComments } = window.oMessengerSelectors.RECORDER;
+        $(recorderComments)
+            .css('max-width', fWidth ? fWidth : $(recorderComments).parent().width());
+    };
+
+    oMessenger.prototype.leaveLot = function (iLotId) {
+        const _this = this,
+            { talksListItems } = window.oMessengerSelectors.TALKS_LIST;
+
+        if (!iLotId)
+            return false;
+
+        $.post('modules/?r=messenger/leave', { lot: iLotId }, function (oData) {
+            bx_alert(oData.message);
+            if (!parseInt(oData.code))
+                _this.searchByItems(() => $(talksListItems).length ? $(talksListItems).first().click() : '');
+        }, 'json');
+    };
+
+    oMessenger.prototype.muteLot = function (iLotId, oEl) {
+        const iVal = parseInt($(oEl).data('value'));
+
+        if (iVal)
+            $('i.sys-icon', oEl)
+                .removeClass('bell-slash').addClass('bell');
+        else
+            $('i', oEl)
+                .removeClass('bell').addClass('bell-slash');
+
+        this.iMuted = +!iVal;
+        $(oEl).data('value', this.iMuted);
+
+        $.post('modules/?r=messenger/mute', { lot: iLotId }, function (oData) {
+            if (typeof oData.code !== 'undefined')
+                $(oEl)
+                    .attr('title', oData.title)
+                    .find('.title')
+                    .text(oData.title)
+        }, 'json');
+    }
+
+    oMessenger.prototype.starLot = function (iLotId, oEl) {
+        const iVal = parseInt($(oEl).data('value'));
+
+        if (!iVal)
+            $('i', oEl)
+                .addClass('fill');
+        else
+            $('i', oEl)
+                .removeClass('fill');
+
+        $(oEl).data('value', +!iVal);
+        $.post('modules/?r=messenger/star', { lot: iLotId }, function (oData) {
+            if (typeof oData.code !== 'undefined')
+                $(oEl)
+                    .attr('title', oData.title)
+                    .find('.title')
+                    .text(oData.title)
+        }, 'json');
+    }
+
+    oMessenger.prototype.viewJot = function (iJotId) {
+        const _this = this,
+            { conversationBody } = window.oMessengerSelectors.HISTORY,
+            { jotMessage, jotDeleted, jotHidden } = window.oMessengerSelectors.JOT,
+            oObject = $(jotMessage, $('div[data-id="' + iJotId + '"]', conversationBody));
+
+        if (!oObject)
+            return;
+
+        if ($(jotHidden, oObject).length) {
+            if ($(jotHidden, oObject).is(':hidden'))
+                $(jotHidden, oObject).fadeIn('slow');
+            else
+                $(jotHidden, oObject).fadeOut();
+
+            return false;
+        }
+
+        bx_loading($(jotDeleted, oObject), true);
+        $.post('modules/?r=messenger/view_jot', { jot: iJotId }, function (oData) {
+            if (!parseInt(oData.code) && oData.html.length)
+                $(jotDeleted, oObject)
+                    .append(
+                        $(oData.html)
+                            .fadeIn('slow')
+                    );
+
+            bx_loading($(jotDeleted, oObject), false);
+        }, 'json');
+    };
+
+    oMessenger.prototype.calculatePositionTop = function (oJot, oObject, bLocal) {
+        const { talkBlock } = window.oMessengerSelectors.HISTORY;
+        const iHeight = oObject.height(),
+            bIsFileMenu = $(oObject).hasClass('file-menu'),
+            iParentHeight = oJot.closest(this.sTalkAreaWrapper).height(),
+            iScrollTop = oJot.closest(talkBlock).scrollTop(),
+            iTop = oJot.position().top - iScrollTop;
+
+        if (bIsFileMenu)
+            return -iHeight / 2;
+
+        let iTopPos = 0;
+        if ((iParentHeight > iHeight) && ((iParentHeight - iTop) < iHeight / 2))
+            iTopPos = iTop - iHeight;
+        else if (iTop > iHeight / 2)
+            iTopPos = iTop - iHeight / 2;
+
+        return iTopPos - (bLocal ? iTop : 0); // use this local in case if it is some internal popup not the global like reaction popup
+    };
+
+    oMessenger.prototype.onAddReaction = function (oObject, bNear) {
+        const _this = this,
+            { jotMain } = window.oMessengerSelectors.JOT,
+            oJot = $(oObject).closest(jotMain),
+            iJotId = oJot.data('id') || 0;
+
+        if (window.oMessengerEmoji)
+            window.oMessengerEmoji.emojiCall((oComponent) => ({
+                top: _this.calculatePositionTop(oJot, oComponent),
+                left: bNear && !oMUtils.isMobile() ? $(oObject).position().left : 0,
+                position: 'absolute'
+            }), () => _this.oActiveEmojiObject = { 'type': 'reaction', 'param': iJotId });
+    };
+
+    oMessenger.prototype.deleteLot = function (iLotId) {
+        const _this = this,
+            { talksListItems, talkItem } = window.oMessengerSelectors.TALKS_LIST,
+            { mainTalkBlock } = window.oMessengerSelectors.HISTORY,
+            { blockContainer, blockMenu } = window.oMessengerSelectors.SYSTEM;
+
+        if (iLotId)
+            $.post('modules/?r=messenger/delete', { lot: iLotId }, function ({ code }) {
+                if (+code === 1)
+                    window.location.reload();
+                else if (!code) {
+                    if (_this.isBlockVersion())
+                        window.location.reload();
+                    else {
+                        $(`[data-lot="${iLotId}"]${talkItem}`).fadeOut(() => {
+                            if ($(talksListItems).length > 0) {
+                                $(talksListItems).first().click();
+                                if (oMUtils.isMobile())
+                                    _this.oMenu.showHistoryPanel();
+                            } else {
+                                $(mainTalkBlock)
+                                    .closest(blockContainer)
+                                    .find(blockMenu)
+                                    .html('');
+                            }
+                        }).remove()
+                    }
+                    ;
+
+                    _this.broadcastMessage({
+                        action: 'msg',
+                        addon: 'remove_lot',
+                        lot: iLotId
+                    });
+                }
+            }, 'json');
+    };
+
+    oMessenger.prototype.clearLot = function (iLotId) {
+        const _this = this,
+            { mainTalkBlock, conversationBody } = window.oMessengerSelectors.HISTORY;
+
+        if (iLotId) {
+            bx_loading($(mainTalkBlock), true);
+            $.post('modules/?r=messenger/clear_history', { lot: iLotId }, function ({ code, message }) {
+                if (!parseInt(code)) {
+                    bx_loading($(mainTalkBlock), false);
+                    $(conversationBody).html('');
+                    _this.broadcastMessage({
+                        action: 'msg',
+                        addon: 'clear'
+                    });
+                } else
+                    bx_alert(message);
+            }, 'json');
+        }
+    };
+
+    oMessenger.prototype.cancelEdit = function (oObject) {
+        const { jotMain, jotMessage } = window.oMessengerSelectors.JOT;
+
+        if (this.lastEditText.length);
+        {
+            $(oObject)
+                .closest(jotMain)
+                .find(jotMessage)
+                .html(this.lastEditText)
+                .parent()
+                .linkify(true, true); // don't update attachment for message and don't broadcast as new message*/
+
+            if (this.oActiveEditQuill) {
+                this.oActiveEditQuill.disable();
+                this.oActiveEditQuill = null;
+            }
+        }
+
+        this.oEditor.focus();
+    };
+
+    oMessenger.prototype.onSaveJotItem = function (oObject) {
+        const _this = this,
+            { jotMain } = window.oMessengerSelectors.JOT,
+            oJot = $(oObject).parents(jotMain),
+            iJotId = oJot.data('id') || 0;
+
+        if (!iJotId)
+            return false;
+
+        $.post('modules/?r=messenger/save_jot_item', { jot: iJotId }, function ({ code, msg, status }) {
+            if (code && msg)
+                bx_alert(msg);
+            else
+                $('.bx-menu-item-title', oObject).text(!status ? _t('_bx_messenger_jot_menu_save') : _t('_bx_messenger_jot_menu_remove_save'));
+        });
+    }
+
+    oMessenger.prototype.saveJot = function (oObject) {
+        const _this = this,
+            { jotMessageBody, jotMain, jotMessage, jotIconsArea, jotIconsEditIcon } = window.oMessengerSelectors.JOT,
+            oJot = $(oObject).parents(jotMain),
+            iJotId = oJot.data('id') || 0,
+            sMessage = _this.oActiveEditQuill && _this.oActiveEditQuill.root.innerHTML.replace("<p><br></p>", "");
+
+        if (!_this.oActiveEditQuill.getText().trim().length) {
+            oJot.closest(jotMessageBody).addClass('hidden');
+            return false;
+        }
+
+        if (sMessage.localeCompare(_this.lastEditText) === 0) {
+            _this.cancelEdit(oObject);
+            return false;
+        }
+
+        if (iJotId) {
+            $.post('modules/?r=messenger/edit_jot', { jot: iJotId, message: sMessage }, function ({ code, html }) {
+                if (!parseInt(code)) {
+                    $(jotMessage, oJot)
+                        .html(sMessage)
+                        .parent()
+                        .linkify(false, true) // update attachment for the message, but don't broadcast as new message
+                        .end();
+
+                    if (!$(oJot).find(jotIconsEditIcon).length)
+                        $(oJot).find(jotIconsArea).prepend(html || '');
+
+                    const oInfo = {
+                        jot_id: iJotId,
+                        addon: 'edit'
+                    };
+
+                    if (!_this.isBlockVersion())
+                        _this.upLotsPosition($.extend(oInfo, _this.oSettings));
+
+                    _this.broadcastMessage(oInfo);
+                }
+            }, 'json');
+
+            return true;
+        }
+
+        return false;
+    };
+
+    oMessenger.prototype.cleanReplayArea = function () {
+        const { textArea, replyAreaMessage } = window.oMessengerSelectors.TEXT_AREA;
+
+        this.iReplyId = 0;
+
+        $(replyAreaMessage, textArea)
+            .empty()
+            .parent()
+            .hide();
+
+        this.oStorage.deleteLotItem(this.oSettings.lot, 'reply');
+    }
+
+    oMessenger.prototype.getReplyPreview = function (iJotId) {
+        const { JOT, ATTACHMENTS, GIPHY } = window.oMessengerSelectors;
+        const oJot = $(`${JOT.jotMain}[data-id="${iJotId}"]`);
+
+        if (!oJot.length) return '...';
+
+        const sMessage = oJot.find(JOT.jotMessage).text().trim();
+        if (sMessage) {
+            const iMaxLength = oMUtils.isMobile() ? this.iMaxReplyLength / 2 : this.iMaxReplyLength;
+            return sMessage.length > iMaxLength ? sMessage.substring(0, iMaxLength) + '...' : sMessage;
+        }
+
+        const oFiles = oJot.find(ATTACHMENTS.attachmentArea);
+        if (!oFiles.length) return '...';
+
+        const oImage = oFiles.find(`${ATTACHMENTS.attachmentImages} img, ${GIPHY.giphyImages} img`).first();
+        if (oImage.length) return oImage.clone();
+
+        const sVideoPoster = oFiles.find(`${ATTACHMENTS.videoAttachment} video`).first().prop('poster');
+        if (sVideoPoster) return $('<img>').attr('src', sVideoPoster);
+
+        const oAudioTitle = oFiles.find(`${ATTACHMENTS.audioAttachment} .audio .title`).first();
+        if (oAudioTitle.length) {
+            const sName = oAudioTitle.text().trim() || '...';
+            const sReplyAudioText = _t('_bx_messenger_reply_audio_file', sName).replace(/&quot;/g, '"');
+            return $('<span>').text(sReplyAudioText);
+        }
+
+        const oFile = oFiles.find(ATTACHMENTS.fileAttachment).first();
+        if (oFile.length) return oFile.clone();
+
+        return '...';
+    };
+
+    oMessenger.prototype.replyJot = function (oObject, iJotId) {
+        const { textArea, replyAreaMessage } = window.oMessengerSelectors.TEXT_AREA,
+            { jotMain } = window.oMessengerSelectors.JOT,
+            oJot = $(oObject).closest(jotMain),
+            _this = this;
+
+        if (!oJot.length) {
+            if (iJotId) {
+                $.post('modules/?r=messenger/get_jot_preview', { jot: iJotId }, function ({ code, html }) {
+                    if (!parseInt(code) && typeof fFunc === 'undefined')
+                        fFunc(html);
+
+                }, 'json');
+            } else
+                return;
+        } else {
+            _this.iReplyId = oJot.data('id');
+            $(replyAreaMessage, textArea)
+                .html(_this.getReplyPreview(_this.iReplyId))
+                .parent()
+                .css('display', 'flex');
+
+            _this.oStorage.saveLotItem(_this.oSettings.lot, _this.iReplyId, 'reply');
+            if (_this.oEditor)
+                setTimeout(() => _this.oEditor.focus(), 100);
+        }
+    }
+
+    oMessenger.prototype.jumpToJot = function (iJumpJotId, fCallback) {
+        const iJotId = iJumpJotId ? iJumpJotId : this.iReplyId,
+            { jotMain, selectedJot } = window.oMessengerSelectors.JOT,
+            _this = this;
+
+        if (iJotId) {
+            const oJot = $(`${jotMain}[data-id="${iJotId}"]`);
+            if (oJot.length)
+                this.updateScrollPosition('center', undefined, oJot, () => oJot.addClass(selectedJot.substr(1)));
+            else {
+                this.iSelectedJot = iJotId;
+                this.loadJotsForLot(this.oSettings.lot, iJotId, fCallback);
+            }
+        }
+    }
+
+    oMessenger.prototype.editJot = function (oObject) {
+        const { jotMain, jotMessage, jotMessageBody } = window.oMessengerSelectors.JOT,
+            oJot = $(oObject).closest(jotMain),
+            iJotId = oJot.data('id') || 0,
+            _this = this;
+
+        if ($(this.sEditJotArea).length)
+            $(this.sEditJotArea).fadeOut().remove();
+
+        oJot.closest(jotMessageBody).removeClass('hidden');
+        if (iJotId) {
+            bx_loading($(jotMessage, oJot).parent(), true);
+            $.post('modules/?r=messenger/edit_jot_form', { jot: iJotId }, function ({ code, html }) {
+                bx_loading($(jotMessage, oJot).parent(), false);
+                if (!parseInt(code)) {
+                    const sTmpText = $(jotMessage, oJot).html();
+                    _this.lastEditText = sTmpText.length ? sTmpText : $(_this.sEditJotAreaId, html).html();
+                    const updateScrollFunction = () => {
+                        const fMaxHeight = parseInt($(_this.sEditJotAreaId).css('max-height'));
+                        if (_this.oActiveEditQuill.root.clientHeight >= fMaxHeight)
+                            $(_this.sEditJotAreaId).css('overflow-y', 'auto');
+                        else
+                            $(_this.sEditJotAreaId).css('overflow-y', 'visible');
+                    };
+
+                    $(jotMessage, oJot)
+                        .html(html)
+                        .find(_this.sEditJotArea)
+                        .fadeIn('slow', function () {
+                            const __this = this;
+                            const oEditEditor = _this.initTextEditor({
+                                selector: _this.sEditJotAreaId,
+                                onEnter: () => {
+                                    if (!(oMUtils.isMobileDevice() || oMUtils.isUnaMobileApp())) {
+                                        _this.saveJot($(_this.sEditJotAreaId));
+                                        _this.oEditor.focus();
+                                        return true;
+                                    }
+
+                                    return false;
+                                },
+                                onChange: () => updateScrollFunction(),
+                                onESC: () => {
+                                    _this.cancelEdit(__this);
+                                    _this.oEditor.focus();
+                                }
+                            });
+
+                            _this.oActiveEditQuill = oEditEditor.oEditor;
+                            oEditEditor.focus();
+
+                            if (oJot.is(':last-child'))
+                                _this.updateScrollPosition('bottom');
+                        });
+                }
+            }, 'json');
+        }
+    };
+
+    oMessenger.prototype.copyJotLink = function (oObject) {
+        const _this = this,
+            { jotMain } = window.oMessengerSelectors.JOT,
+            iJotId = $(oObject)
+                .parents(jotMain)
+                .data('id') || 0,
+            oTextArea = document.createElement('textArea');
+
+        // IOS mobile devices only
+        if (_this.aPlatforms.includes(navigator.platform) && oMUtils.isMobileDevice()) {
+            bx_prompt('&nbsp;', _this.sJotUrl + iJotId, () => {
+                $('#bx-popup-prompt-value').get(0).setSelectionRange(0, 999999);
+                if (document.queryCommandSupported("copy")) {
+                    document.execCommand('copy');
+                    $('#bx-popup-prompt').dolPopupHide();
+                    $('#bx-popup-prompt-value').get(0).blur();
+                }
+            }, undefined, {
+                ok: {
+                    title: _t('_bx_messenger_share_jot')
+                }
+            });
+
+            // iOS only selects "form" elements with SelectionRange
+            return;
+        }
+
+        oTextArea.readOnly = false;
+        oTextArea.contentEditable = true;
+        oTextArea.value = _this.sJotUrl + iJotId;
+        document.body.appendChild(oTextArea);
+
+        try {
+            if (document.body.createTextRange) {// IE
+                const oTextRange = document.body.createTextRange();
+                oTextRange.moveToElementText(oTextArea);
+                oTextRange.select();
+                oTextRange.execCommand("Copy");
+            } else if (window.getSelection && document.createRange) {// non-IE
+                const oRange = document.createRange();
+                oRange.selectNodeContents(oTextArea);
+                const oSel = window.getSelection();
+                oSel.removeAllRanges();
+                oSel.addRange(oRange); // Does not work for Firefox if a textarea or input
+                oTextArea.select(); // Firefox will only select a form element with select()
+
+                if (document.queryCommandSupported("copy"))
+                    document.execCommand('copy');
+            }
+        } catch (e) {
+            console.log(e.toString());
+        }
+
+        document.body.removeChild(oTextArea);
+    };
+
+    $.fn.setRandomBGColor = function () {
+        $('img', this).each(function () {
+            let hex = Math.floor(Math.random() * 0xFFFFFF);
+            $(this).css('background-color', "#" + ("000000" + hex.toString(16)).substr(-6));
+        });
+
+        return this;
+    };
+
+    $.fn.bxMsgTime = function () {
+        $(this)
+            .bxTime()
+            .find('time')
+            .each(function () {
+                $(this).prop('title', $(this).text());
+            });
+
+        return this;
+    };
+
+    /**
+     * Convert plan text links/emails to urls, mailto
+     *@param string sText text of the message
+     *@param bool bDontBroadcast don't broadcast event as new message
+     *@param bool bDontAddAttachment don't add repost link to the jot
+     */
+    $.fn.linkify = function (bDontAddAttachment, bDontBroadcast) {
+        const { jotMessage } = window.oMessengerSelectors.JOT,
+            { attachmentArea } = window.oMessengerSelectors.ATTACHMENTS;
+
+        let sUrlPattern = /((https?):\/\/[^"<\s]+)(?![^<>]*>|[^"]*?<\/a)/gim,
+            // www, http, https
+            sPUrlPattern = /(^|[^\/"'])(www\.[\S]+(\b|$))/gim,
+            // Email addresses
+            sEmailPattern = /([\w.]+@[a-zA-Z_-]+?(?:\.[a-zA-Z]{2,6}))(?![^<>]*>|[^"]*?<\/a)/gim,
+            sJotLinkPattern = new RegExp(_oMessenger.sJotUrl.replace('?', '\\?') + '\\d+', 'i');
+
+        const oJot = $(jotMessage, this).first();
+        if (!$(oJot).length)
+            return this;
+
+        let sUrl = '',
+            sText = $(oJot)
+                .html()
+                .replace(sUrlPattern, function (str) {
+                    sUrl = str;
+                    return !sJotLinkPattern.test(str) ? _oMessenger.sEmbedTemplate.replace(/__url__/g, str) : `<a href="${str}">${str}</a>`;
+                })
+                .replace(sPUrlPattern, function (str, p1, p2) {
+                    sUrl = 'http://' + p2;
+                    return p1 + (!sJotLinkPattern.test(str) ? _oMessenger.sEmbedTemplate.replace(/__url__/g, p2) : `<a href="${p2}">${p2}</a>`);
+                })
+                .replace(sEmailPattern, str => `<a href="mailto:${str}">${str}</a>`);
+
+        if (!sUrl) {
+            const aMatch = sText.match(/<a[^"](?!class="bx-mention[^"]*").*href="(https?:\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|])".*a>/);
+            if (Array.isArray(aMatch) && aMatch.length >= 2)
+                sUrl = aMatch[1];
+        }
+
+        $(oJot).html(sText);
+        if ($(oJot).siblings(attachmentArea).length)
+            return this;
+
+        if (sUrl.length) {
+            if (!bDontBroadcast)
+                _oMessenger.iAttachmentUpdate = true;
+
+            $(oJot)
+                .attacheLinkContent(sUrl, bDontAddAttachment,
+                    function () {
+                        if (!bDontBroadcast) {
+                            _oMessenger.broadcastMessage();
+                            _oMessenger.updateScrollPosition('bottom');
+                            _oMessenger.iAttachmentUpdate = false;
+                        }
+                    });
+        } else {
+            $(oJot)
+                .siblings(_oMessenger.attachmentArea)
+                .remove() /* remove attachment block if exists*/
+        }
+
+        return this;
+    }
+
+    /**
+     * Add attachment for the urls
+     *@param string sUrl internal or external link
+     *@param bool bDontAddAttachment don't attached link to the message jot to parse on html page
+     *@param function fCallback call when ajax request is completed
+     *@return object this
+     */
+    $.fn.attacheLinkContent = function (sUrl, bDontAddAttachment, fCallback) {
+        const _this = this,
+            { jotMain } = window.oMessengerSelectors.JOT,
+            { attachmentBlock } = window.oMessengerSelectors.ATTACHMENTS;
+
+        $.post('modules/?r=messenger/parse_link',
+            {
+                link: sUrl,
+                jot_id: $(_this)
+                    .parents(jotMain)
+                    .data('id'),
+                dont_attach: !!bDontAddAttachment
+            },
+            function (oData) {
+                if (!parseInt(oData.code))
+                    $(_this)
+                        .siblings(attachmentBlock)
+                        .remove() /* remove attachment if exists */
+                        .end()
+                        .after(oData.html);
+
+                if (typeof fCallback == 'function')
+                    fCallback();
+            },
+            'json');
+
+        return this;
+    }
+
+    /**
+     * Check for not processed videos and update them, if they are ready
+     */
+    oMessenger.prototype.updateProcessedMedia = function () {
+        const _this = this,
+            fMedia = (sMediaType, sType) => document.createElement(sMediaType.toLowerCase()).canPlayType(sType),
+            { conversationBody } = window.oMessengerSelectors.HISTORY,
+            { videoAttachment, audioAttachment } = window.oMessengerSelectors.ATTACHMENTS;
+
+
+        let aMedia = [];
+        $(`${videoAttachment},${audioAttachment}`, conversationBody).each(
+            function () {
+                let __this = $(this);
+                $(`${_this.sTmpVideoFile}, audio`, this).each(function () {
+                    if (!$('source', this).prop('src') || fMedia($(this).prop('tagName'), $('source', this).prop('type')) === '')
+                        aMedia.push($(__this).data('media-id'));
+                });
+            });
+
+        if (aMedia.length)
+            $.post('modules/?r=messenger/get_processed_media', { media: aMedia },
+                function (oData) {
+                    for (let i = 0; i < aMedia.length; i++) {
+                        if (typeof oData[aMedia[i]] !== 'undefined') {
+                            $(`[data-media-id="${aMedia[i]}"] ${_this.sTmpVideoFile}`, conversationBody)
+                                .replaceWith(oData[aMedia[i]]);
+
+                            $('[data-media-id="' + aMedia[i] + '"] .audio .file', conversationBody)
+                                .replaceWith(oData[aMedia[i]]);
+                        }
+                    }
+                }, 'json');
+    }
+
+    /**
+     * Add attachment to the message
+     *@param string sUrl internal or external link
+     *@return object this
+     */
+    oMessenger.prototype.attacheFiles = function (iJotId, bBottom = true, fCallback) {
+        const _this = this,
+            { talkListJotSelector } = window.oMessengerSelectors.JOT,
+            { attachmentArea } = window.oMessengerSelectors.ATTACHMENTS;
+
+        _this.iAttachmentUpdate = true;
+        $.post('modules/?r=messenger/get_attachment', { jot_id: iJotId }, function ({ html, code }) {
+            if (!+code) {
+                const oAttachment = $(html),
+                    sID = `[data-id="${iJotId}"]`;
+                    oJot = $(sID);
+
+                $(attachmentArea, sID).remove();
+
+                $(_this.sReactionsArea, sID)
+                    .before(
+                        oAttachment
+                            .waitForImages(() => {
+                                if (typeof fCallback === 'function')
+                                    fCallback();
+
+                                if (bBottom || +$(talkListJotSelector).last().data('id') === +iJotId)
+                                    _this.updateScrollPosition('bottom');
+                            })
+                    );
+
+                oJot.initJotIcons().initAccordion();
+                oAttachment.bxProcessHtml();
+                _this.broadcastMessage();
+            }
+
+            _this.iAttachmentUpdate = false;
+        },
+            'json');
+
+        return this;
+    }
+
+    /**
+     * Select lot in left side column when member clicks on it
+     *@param object oBlock selected lot
+     */
+    oMessenger.prototype.selectLotEmit = function (oBlock) {
+        const { active, talkItemBubble, unreadLot } = window.oMessengerSelectors.TALKS_LIST;
+
+        oBlock
+            .addClass(active)
+            .removeClass(unreadLot)
+            .siblings()
+            .removeClass(active)
+            .end()
+            .find(talkItemBubble)
+            .addClass('hidden');
+
+    }
+
+    /**
+     * Change page's favicon
+     *@param boolean bEnable
+     */
+    oMessenger.prototype.updatePageIcon = function (bEnable, iLot) {
+        const { talksListItems } = window.oMessengerSelectors.TALKS_LIST,
+            iCurrentLot = $(talksListItems).first().data('lot');
+
+        let iUnreadLotsCount = +this.oNotifications.inbox;
+
+        if (iUnreadLotsCount === 1 && iLot === iCurrentLot && (!oMUtils.isMobile()))
+            iUnreadLotsCount = 0;
+
+        this.notifyNewUnreadChats(iUnreadLotsCount);
+
+        $(document).prop('title', $(document).prop('title').replace(/\(\d+\)$/g, ''));
+        if (bEnable === true || iUnreadLotsCount) {
+            $('link[rel="shortcut icon"]').attr('href', this.sInfoFavIcon);
+            $(document).prop('title', $(document).prop('title') + ` (${iUnreadLotsCount})`);
+        } else if (bEnable === false || !iUnreadLotsCount)
+            $('link[rel="shortcut icon"]').attr('href', this.sDefaultFavIcon);
+    };
+
+    $.fn.waitForImages = function (fCallback) {
+        const { attachmentImages } = window.oMessengerSelectors.ATTACHMENTS,
+            { giphyImages } = window.oMessengerSelectors.GIPHY;
+
+        const aImg = $(`${giphyImages} img, picture img, ${attachmentImages} img`, $(this));
+        let iTotalImg = aImg.length;
+
+        const waitImgLoad = () => {
+            iTotalImg--;
+            if (!iTotalImg && typeof fCallback === 'function') {
+                fCallback(this);
+            }
+        };
+
+        if (!iTotalImg) {
+            if (typeof fCallback === 'function')
+                setTimeout(() => fCallback(this), 0);
+        } else
+            aImg.one('load', function () {
+                waitImgLoad();
+            }).each(function () {
+                if (this.complete)
+                    $(this).load();
+            });
+
+        return this;
+    };
+
+    oMessenger.prototype.markJotsAsRead = function (iLotId, fCallback) {
+        $.post('modules/?r=messenger/mark_jots_as_read', { lot: iLotId }, function (oData) {
+            if (!parseInt(oData.code) && typeof fCallback === 'function')
+                fCallback();
+        }, 'json');
+    };
+
+    oMessenger.prototype.updateCounters = function (iNumber, bForceUpdate = false) {
+        const { talkItem } = window.oMessengerSelectors.TALKS_LIST,
+            { talkBlock, mainScrollArea, unreadJotsCounter } = window.oMessengerSelectors.HISTORY,
+            { talkListJotSelector } = window.oMessengerSelectors.JOT,
+        iCounter = bForceUpdate ? iNumber : +$(unreadJotsCounter).text(),
+            { lot, area_type, group_id } = this.oSettings;
+
+        if (!iCounter)
+            $(unreadJotsCounter).hide();
+
+        if (bForceUpdate) {
+            $(unreadJotsCounter).text(iCounter);
+            if (iCounter)
+                $(unreadJotsCounter).show();
+            else {
+                const iScrollPosition = $(talkBlock).prop('scrollHeight') - $(talkBlock).prop('clientHeight') - $(talkBlock).scrollTop();
+                const iHeight = $(talkListJotSelector).last().height();
+                if (iScrollPosition <= iHeight)
+                    $(mainScrollArea).fadeOut();
+            }
+        }
+
+        this.iUnreadJotsNumber = iCounter;
+
+        if (!$(unreadJotsCounter).is(':visible')) {
+            $(this).updateMenuBubbles(lot, { type: area_type, group_id }, false);
+            this.selectLotEmit($(`[data-lot="${lot}"]${talkItem}`));
+        }
+    }
+
+    oMessenger.prototype.broadcastView = function (iJotId) {
+        const
+            { conversationBody } = window.oMessengerSelectors.HISTORY,
+            { talkListJotSelector } = window.oMessengerSelectors.JOT,
+            iLastJotId = $(talkListJotSelector).last().data('id'),
+            iJot = iLastJotId === this.iLastReadJotId ? iLastJotId : this.iLastUnreadJot,
+            oJot = $(`[data-id="${iJot}"]`, conversationBody);
+
+        if (!+oJot.data('my') && +oJot.data('new')) {
+            $.get('modules/?r=messenger/viewed_jot', { jot_id: iJot }, ({ unread_jots, last_unread_jot }) => {
+                this.updateCounters(unread_jots, true);
+                this.iLastUnreadJot = last_unread_jot;
+            }, 'json');
+
+            oJot.data('new', 0);
+
+            return this.broadcastMessage({
+                jot_id: iJot,
+                addon: 'check_viewed'
+            });
+        }
+
+    };
+
+    oMessenger.prototype.showSearchCounter = function (iLotId) {
+        const _this = this,
+            { searchCriteria } = window.oMessengerSelectors.TALKS_LIST,
+            { searchItems, searchScrollArea } = window.oMessengerSelectors.HISTORY,
+            oObject = $(searchScrollArea);
+
+        if (!$(searchCriteria).val().length) {
+            oObject.find(searchItems).text(0).end().fadeOut();
+            return false;
+        }
+
+        let iCounter = 0;
+        if ($(searchCriteria).val().length && _this.aSearchJotsList !== null) {
+            if (typeof _this.aSearchJotsList[iLotId] !== 'undefined') {
+                const { list } = _this.aSearchJotsList[iLotId] || {};
+                iCounter = list?.length;
+            }
+        }
+
+        if (iCounter)
+            oObject.fadeIn();
+        else
+            oObject.fadeOut();
+
+        oObject.find(searchItems).text(iCounter);
+        return false;
+    }
+
+    oMessenger.prototype.editOnClick = function () {
+        const _this = this,
+            { bxTitle } = window.oMessengerSelectors.SYSTEM,
+            { messengerHistoryBlock } = window.oMessengerSelectors.HISTORY;
+
+        if (!oMUtils.isMobile())
+            $(bxTitle, messengerHistoryBlock).dblclick(() => _this.createList('edit'));
+    }
+
+    /**
+     * Load history for selected lot
+     * @param iLotId
+     * @param iJotId
+     * @param bDontChangeCol
+     * @param fCallback
+     * @param bMarkAsRead
+     */
+    oMessenger.prototype.loadTalk = function (iLotId, iJotId, fCallback = null, bMarkAsRead = false) {
+        const _this = this,
+            {
+                mainTalkBlock,
+                messengerHistoryBlock,
+                createTalkArea,
+                tableWrapper,
+                conversationBlockHistory,
+                historyColumn,
+                talkBlock,
+                conversationBody,
+                selectedJot
+            } = window.oMessengerSelectors.HISTORY,
+            { talksListItems, talkItem } = window.oMessengerSelectors.TALKS_LIST,
+            { textArea } = window.oMessengerSelectors.TEXT_AREA,
+            { groupsPanel } = window.oMessengerSelectors.TALK_BLOCK,
+            { talkListJotSelector, jotMain } = window.oMessengerSelectors.JOT,
+            { blockHeader, blockContainer } = window.oMessengerSelectors.SYSTEM,
+            { lot, area_type } = this.oSettings,
+            oLotBlock = $(`[data-lot="${iLotId}"]${talkItem}`),
+            fEmpty = { done: (r) => r() };
+
+        if (!iLotId)
+            return fEmpty;
+
+        if (_this.isBlockVersion()) {
+            _this.oMenu.toggleBlockGroupsPanel();
+            $(historyColumn).html('');
+        } else {
+            _this.selectLotEmit(oLotBlock);
+            if (oLotBlock) {
+                if (oMUtils.isMobile()) {
+                    if (!_this.oMenu.isHistoryColActive())
+                        _this.oMenu.showHistoryPanel();
+                }
+
+                _this.updateScrollPosition('bottom');
+                $(`${blockHeader}, ${talkBlock}`, historyColumn).html('');
+            }
+        }
+
+        _this.disableCreateList();
+
+        // to change active lot ID in the same time when user click on load but not when history is loaded
+        _this.oSettings.lot = iLotId;
+        _this.blockSendMessages(true);
+
+        $(_this.sDateNavigator).hide();
+        bx_loading($(conversationBlockHistory), true);
+        return $.post('modules/?r=messenger/load_talk', {
+            lot_id: iLotId,
+            jot_id: iJotId,
+            mark_as_read: +bMarkAsRead,
+            area_type,
+            is_block: _this.isBlockVersion()
+        },
+            function ({
+                title,
+                history,
+                header,
+                code,
+                unread_jots,
+                last_unread_jot,
+                text_area,
+                muted,
+                talks_list,
+                params
+            }) {
+                if (~code) {
+                    _this.iMuted = +muted;
+                    _this.blockSendMessages(false);
+
+                    const oMessengerBlock = $(messengerHistoryBlock).length ? messengerHistoryBlock : $(mainTalkBlock).closest(blockContainer);
+                    if (typeof text_area !== 'undefined' && text_area.length) {
+                        if (!$(textArea, oMessengerBlock).length)
+                            $(tableWrapper, oMessengerBlock).append(text_area);
+                        else
+                            $(tableWrapper, oMessengerBlock).find(textArea).replaceWith(text_area);
+                    } else
+                        $(tableWrapper, oMessengerBlock).find(textArea).remove();
+
+                    $(talkBlock).css('visibility', 'hidden');
+                    $(oMessengerBlock)
+                        .find(blockHeader)
+                        .replaceWith(header)
+                        .end()
+                        .find(talkBlock)
+                        .html(history)
+                        .bxProcessHtml()
+                        .end()
+                        .bxMsgTime()
+                        .show(
+                            function () {
+                                _this.setPositionOnSelectedJot();
+                                _this.updateLotSettings({
+                                    lot: iLotId,
+                                    last_unread_jot,
+                                    unread_jots,
+                                    group_id: +params?.group_id || 0,
+                                    title
+                                });
+
+                                _this.updateCounters(unread_jots, true);
+                                _this.updatePageIcon(undefined, iLotId);
+                            }
+                        )
+                        .waitForImages(() => {
+                            _this.setPositionOnSelectedJot(() => {
+                                bx_loading($(conversationBlockHistory), false);
+                                $(talkBlock).css('visibility', 'visible');
+                                if (typeof fCallback === 'function')
+                                    fCallback();
+                            });
+                        });
+
+                    if (talks_list) {
+                        $(oMessengerBlock)
+                            .find(groupsPanel)
+                            .replaceWith(talks_list)
+                    }
+
+                    if (typeof title !== 'undefined')
+                        $(document).prop('title', title);
+
+                    if (oLotBlock)
+                        _this.setUsersStatuses(oLotBlock);
+
+                    if (+$(talkListJotSelector).last().data('new')) {
+                        if (!_this.iLastUnreadJot || $(selectedJot, conversationBody).nextAll(jotMain).length < _this.iMaxHistory / 2)
+                            _this.broadcastView($(talkListJotSelector).last().data('id'));
+                    }
+
+                    if (_this.oFilesUploader)
+                        _this.oFilesUploader.removeInstances();
+
+                    // init text area
+                    _this.initTextArea();
+                    _this.oSendPool = new Map();
+
+                    if (_this.oEditor && !oMUtils.isMobile())
+                        setTimeout(() => _this.oEditor.focus(), 1000);
+
+                    _this.showSearchCounter(iLotId);
+
+                    /* ----  End ---- */
+                } else
+                    bx_loading($(conversationBlockHistory), false);
+
+            }, 'json');
+    };
+
+    oMessenger.prototype.onReplyInThread = function (oObject) {
+        const _this = this,
+            { jotMain } = window.oMessengerSelectors.JOT,
+            { blockHeader } = window.oMessengerSelectors.SYSTEM,
+            { conversationBlockHistory, historyColumn, talkBlock } = window.oMessengerSelectors.HISTORY,
+            oJot = $(oObject).closest(jotMain);
+
+        if (oMUtils.isMobile()) {
+            _this.oMenu.showHistoryPanel();
+            $(`${blockHeader}, ${talkBlock}`, historyColumn).html('');
+        }
+
+        // to change active lot ID in the same time when user click on load but not when history is loaded
+        _this.blockSendMessages(true);
+        bx_loading($(conversationBlockHistory), true);
+
+        $.post('modules/?r=messenger/load_thread_talk', { jot_id: oJot.data('id') }, function ({
+            history,
+            header,
+            code,
+            text_area,
+            top_area,
+            lot
+        }) {
+            bx_loading($(conversationBlockHistory), false);
+            if (+code || !+lot)
+                return false;
+
+            return _this.loadTalk(lot);
+        }, 'json');
+    }
+
+    oMessenger.prototype.loadJotsForLot = function (iLotId, iJotId, fCallback) {
+        const _this = this,
+            { talkBlock, mainTalkBlock } = window.oMessengerSelectors.HISTORY;
+
+        bx_loading($(mainTalkBlock), true);
+        $.post('modules/?r=messenger/load_jots', { id: iLotId, jot_id: iJotId }, function ({ code, history }) {
+            bx_loading($(mainTalkBlock), false);
+            if (code === 1)
+                window.location.reload();
+
+            if (!parseInt(code)) {
+                $(mainTalkBlock)
+                    .find(talkBlock)
+                    .html(history)
+                    .initJotIcons()
+                    .initAccordion()
+                    .bxProcessHtml()
+                    .end()
+                    .bxMsgTime()
+                    .waitForImages(() => _this.updateScrollPosition(iJotId ? 'position' : 'bottom', 'fast', $(`[data-id="${iJotId}"]`)));
+
+                if (typeof fCallback == 'function')
+                    fCallback();
+            }
+        }, 'json');
+    }
+    /**
+     * Main send message function, occurs when member send message
+     * @param string sMessage text of the message
+     * @param object mixedObjects, it may be array of files or object with elements
+     * @param function fCallBack
+     */
+    oMessenger.prototype.sendMessage = function (sMessage, mixedObjects, fCallBack) {
+        const _this = this,
+            oParams = Object.assign({}, this.oSettings, _this.getSendAreaAttachmentsIds()),
+            msgTime = new Date(),
+            { talkTitle, textArea, replyAreaMessage, replyArea } = window.oMessengerSelectors.TEXT_AREA,
+            {
+                conversationBody,
+                messengerHistoryBlock,
+                uploaderAreaPlaceholderPrefix
+            } = window.oMessengerSelectors.HISTORY,
+            { blockHeader, blockContainer } = window.oMessengerSelectors.SYSTEM,
+            { threadReplies } = window.oMessengerSelectors.THREAD,
+            {
+                talkListJotSelector,
+                jotMessageBody,
+                jotMain,
+                jotContainer,
+                jotMessage,
+                jotTitle,
+                jotAvatar
+            } = window.oMessengerSelectors.JOT;
+
+        oParams.tmp_id = msgTime.getTime();
+        if (_this.oFilesUploader) {
+            oParams.files = _this.oFilesUploader.getAllFiles();
+
+            if (oParams.files.length && !_this.oFilesUploader.isReady()) {
+                _this.aUploaderQueue[_this.oFilesUploader.name()] = { id: oParams.tmp_id, lot: this.oSettings.lot };
+            }
+        }
+
+        if (typeof mixedObjects !== 'undefined' && Array.isArray(mixedObjects.files))
+            oParams.files = mixedObjects.files;
+
+        if (_this.iThreadId)
+            oParams.parent = _this.iThreadId;
+
+        oParams.participants = _this.getParticipantsList();
+
+        oParams.message = $.trim(sMessage);
+        if (!oParams.message.length && !oParams.files.length && typeof oParams.giphy === 'undefined')
+            return;
+
+        // talk's title instead of users list
+        if ($(talkTitle).length)
+            oParams.title = $(talkTitle).val();
+
+        // remove MSG (if it exists) from clean history page
+        if ($(blockContainer, conversationBody).length)
+            $(blockContainer, conversationBody).remove();
+
+        if (oParams.message.length > this.iMaxLength)
+            oParams.message = oParams.message.substr(0, this.iMaxLength);
+
+        if ((oParams.message || oParams.files.length || (typeof oParams.giphy !== 'undefined' && oParams.giphy.length)) && _this.bAllowAttachMessages) {
+            let sUploadingArea = '';
+            if (_this.oFilesUploader && oParams.files.length && !_this.oFilesUploader.isReady())
+                sUploadingArea = `<div class="${uploaderAreaPlaceholderPrefix}" id="${uploaderAreaPlaceholderPrefix}-${oParams.tmp_id}"></div>`;
+
+            const oUserTemplate = _this.oUsersTemplate.clone();
+            if (oParams.message.length)
+                oUserTemplate.find(jotMessageBody).removeClass('hidden');
+
+            if (_this.iReplyId) {
+                const sContent = $(replyAreaMessage, textArea).html();
+                oUserTemplate.html(
+                    oUserTemplate
+                        .html()
+                        .replace(/{reply_message}/g, sContent)
+                        .replace(/{reply_parent_id}/g, _this.iReplyId)
+                )
+                    .find(replyArea)
+                    .css('display', 'flex');
+            } else
+                $(oUserTemplate).find(replyArea).remove();
+
+            if ($(talkListJotSelector).last().data('my')) {
+                $(oUserTemplate)
+                    .removeClass('pt-4')
+                    .find(jotTitle)
+                    .remove()
+                    .end()
+                    .find(jotAvatar)
+                    .children()
+                    .remove();
+            }
+
+            const sContent = $(oUserTemplate)
+                .find(jotMessage)
+                .html()
+                .replace('{message}', oParams.message || '');
+
+
+            // append content of the message to the history page
+            if (!_this.bCreateNew)
+                $(conversationBody)
+                    .append(oUserTemplate
+                        .attr('data-tmp', oParams.tmp_id)
+                        .find(jotMessage)
+                        .html(sContent)
+                        .fadeIn('slow')
+                        .end()
+                        .find(jotContainer)
+                        .append(sUploadingArea)
+                        .end()
+                    );
+
+            const oLastLine = $(oUserTemplate).find('span.time').prev('p');
+            if (oLastLine.length)
+                $(oUserTemplate).find('span.time').appendTo(oLastLine);
+
+            if (sUploadingArea.length) {
+                _this.oFilesUploader.move(`#${uploaderAreaPlaceholderPrefix}-${oParams.tmp_id}`);
+                _this.createFilesUploader();
+            }
+
+            $('[data-tmp="' + oParams.tmp_id + '"]').initJotIcons().bxProcessHtml();
+        }
+
+        if (_this.iReplyId) {
+            oParams.reply = +_this.iReplyId;
+            _this.cleanReplayArea();
+        }
+
+        if (_this.bCreateNew) {
+            oParams.lot = 0;
+            if (!['groups'].includes(_this.oSettings.area_type)) {
+                oParams.group_id = 0;
+            }
+
+            const aBroadcastData = oMUtils.getBroadcastFields();
+            if (Object.keys(aBroadcastData).length)
+                oParams['broadcast'] = aBroadcastData;
+        } else {
+            if (!+_this.oSettings.lot) {
+                if (!oParams.participants.length) {
+                    if (+_this.iSelectedPersonToTalk) {
+                        oParams.participants.push(_this.iSelectedPersonToTalk);
+                        _this.iSelectedPersonToTalk = 0;
+                    } else {
+                        bx_alert(_t('_bx_messenger_lot_parts_empty'));
+                        return;
+                    }
+                }
+
+                oParams.type = _this.iLotType;
+            }
+        }
+
+        _this.updateScrollPosition('bottom');
+        // save message to database and broadcast to all participants
+        _this.oSendPool.set(oParams.tmp_id, {
+            'promise': null, 'run': () => $.post('modules/?r=messenger/send', oParams, function ({
+                jot_id,
+                header,
+                tmp_id,
+                message,
+                code,
+                time,
+                lot_id,
+                separator
+            }) {
+                switch (parseInt(code)) {
+                    case 0:
+                        const iJotId = parseInt(jot_id), sTime = time || msgTime.toISOString();
+
+                        if (iJotId) {
+                            if (typeof tmp_id !== 'undefined') {
+                                Object.keys(_this.aUploaderQueue).map((sKey) => {
+                                    if (+_this.aUploaderQueue[sKey].id === +tmp_id) {
+                                        _this.aUploaderQueue[sKey].uploading_jot_id = jot_id;
+                                    }
+                                });
+                            }
+
+                            if (typeof lot_id !== 'undefined') {
+                                if (typeof header !== 'undefined')
+                                    $(messengerHistoryBlock)
+                                        .find(blockHeader)
+                                        .replaceWith(header);
+
+                                _this.updateLotSettings({ lot: +lot_id });
+                                _this.updateTalksListArea();
+                                _this.broadcastMessage({
+                                    addon: {
+                                        jot_id: jot_id
+                                    }
+                                });
+
+                                const fLoadTalksCallback = () => _this.loadTalk(lot_id, undefined, () => {
+                                    if (!_this.isBlockVersion())
+                                        _this.upLotsPosition(_this.oSettings);
+
+                                    // trigger to detect new talk creation
+                                    $(document).trigger(jQuery.Event('bx_messenger_talk_created'));
+                                });
+
+                                if (!['inbox', 'direct', 'groups'].includes(_this.oSettings.area_type))
+                                    return _this.loadTalksListByParam({ group: 'direct' }, fLoadTalksCallback);
+                                else
+                                    return fLoadTalksCallback();
+                            }
+
+                            if (!_this.isBlockVersion())
+                                _this.upLotsPosition(_this.oSettings);
+
+                            if (typeof tmp_id !== 'undefined') {
+                                $('[data-tmp="' + tmp_id + '"]', conversationBody)
+                                    .data('id', jot_id)
+                                    .attr('data-id', jot_id)
+                                    .find('time')
+                                    .html('')
+                                    .attr('datetime', sTime)
+                                    .closest(jotMain)
+                                    .bxMsgTime()
+                                    .bxProcessHtml()
+                                    .linkify()
+                                    .find('.time > img')
+                                    .remove()
+                                    .end()
+                                    .find('div[id^="jot-menu-"]')
+                                    .attr('id', `jot-menu-${jot_id}`);
+                            }
+
+                            if (typeof separator !== 'undefined') {
+                                $('[data-tmp="' + tmp_id + '"]', conversationBody).before($(separator).bxTime());
+                                _this.updateScrollPosition('bottom');
+                            }
+
+                            if ((_this.oFilesUploader && oParams.files && _this.oFilesUploader.isReady()) || typeof oParams.giphy !== 'undefined')
+                                _this.attacheFiles(iJotId);
+
+                        }
+
+                        if (_this.iThreadId && $(threadReplies).length) {
+                            let iCounter = parseInt($('>span', threadReplies).text());
+                            $('>span', threadReplies).text(++iCounter);
+                        }
+
+                        if (!_this.iAttachmentUpdate) {
+                            _this.broadcastMessage({
+                                addon: {
+                                    jot_id: jot_id
+                                }
+                            });
+                        }
+                        break;
+
+                    default:
+                        if (message) {
+                            bx_alert(message);
+
+                            if (_this.bCreateNew) { // in case if an error occurs during talk creation
+                                _this.oSendPool.delete(oParams.tmp_id);
+                                _this.oEditor.setContents(_this.aTmpContent.ops);
+
+                                if (_this.oPrevSettings !== null)
+                                    _this.updateLotSettings(_this.oPrevSettings);
+
+                                return;
+                            }
+
+                            $(`[data-tmp="${oParams.tmp_id}"]`, conversationBody).remove();
+                        } else
+                            window.location.reload();
+
+                        _this.setStorageData();
+                        break;
+                }
+
+                if (typeof fCallBack === 'function')
+                    fCallBack(jot_id);
+
+            }, 'json')
+                .done(() => {
+                    if (_this.oSendPool.has(oParams.tmp_id)) {
+                        _this.oSendPool.delete(oParams.tmp_id);
+                        if (_this.oSendPool.size && !+$(`[data-tmp="${oParams.tmp_id}"]`).data('retry'))
+                            for (let sTmp of _this.oSendPool.keys()) {
+                                if (!$(`[data-tmp="${sTmp}"]`).data('retry')) {
+                                    const oThread = _this.oSendPool.get(sTmp);
+                                    if (oThread.promise === null)
+                                        oThread.promise = oThread.run();
+                                    break;
+                                }
+                            }
+                    }
+                })
+                .fail(() => {
+                    for (let sTmp of _this.oSendPool.keys()) {
+                        $(_this.sJotMessageTitle, $(`[data-tmp="${sTmp}"]`))
+                            .find('time img')
+                            .replaceWith(
+                                $(`<a href="javascript:void(0);" class="bx-messenger-send-retry bx-form-warn">
+									<i class="sys-icon exclamation-circle"></i>
+							  		</a>`)
+                                    .on('click', function () {
+                                        if (_this.oSendPool.has(sTmp)) {
+                                            const oThread = _this.oSendPool.get(sTmp);
+                                            oThread.promise = oThread.run();
+
+                                            $(`[data-tmp="${sTmp}"]`)
+                                                .find('time')
+                                                .html(_this.sJotSpinner);
+                                        }
+                                    })
+                            )
+                            .end()
+                            .closest(jotMain)
+                            .data('retry', true);
+                    }
+                })
+        });
+
+        // in case if there are some not sent messages, find the first just sent
+        let bRetry = false, iNewCount = 0;
+        if (_this.oSendPool.size > 1) {
+            for (let sTmp of _this.oSendPool.keys()) {
+                if (+$(`[data-tmp="${sTmp}"]`).data('retry'))
+                    bRetry = true;
+                else
+                    iNewCount++;
+            }
+        }
+
+        if (_this.oSendPool.size === 1 || (bRetry && iNewCount === 1)) {
+            const oThread = _this.oSendPool.get(oParams.tmp_id);
+            if (oThread.promise === null) {
+                if (_this.bCreateNew)
+                    bx_confirm(_t('_bx_messenger_create_talk_confirm'), () => {
+                        _this.oPrevSettings = Object.assign({}, _this.oSettings);
+                        _this.updateLotSettings({ title: oParams.title || '', lot: 0, group_id: oParams.group_id });
+                        oThread.promise = oThread.run();
+                    },
+                        () => {
+                            /* delete _this.aUploaderQueue[_this.oFilesUploader.name()]; */
+
+                            _this.oSendPool.delete(oParams.tmp_id);
+                            _this.oEditor.setContents(_this.aTmpContent.ops);
+                        });
+                else
+                    oThread.promise = oThread.run();
+            }
+        }
+
+        return true;
+    };
+
+    oMessenger.prototype.broadcastMessage = function (oInfo) {
+        const oMessage = Object.assign({
+            lot: this.oSettings.lot,
+            name: this.oSettings.name,
+            user_id: this.oSettings.user_id
+        }, oInfo || {});
+
+        if (this.oRTWSF !== undefined)
+            this.oRTWSF.message(oMessage);
+    };
+
+    /**
+     * Get all participants from users selector area
+     */
+    oMessenger.prototype.getParticipantsList = function () {
+        const list = [],
+            { selectedUsersListInputs } = window.oMessengerSelectors.CREATE_TALK;
+
+        if ($(selectedUsersListInputs).length) {
+            $(selectedUsersListInputs).each(function () {
+                list.push($(this).val());
+            });
+        }
+
+        return list;
+    };
+
+    oMessenger.prototype.stopActiveSound = function () {
+        if (this.oActiveAudioInstance) {
+            this.oActiveAudioInstance.stop();
+            this.oActiveAudioInstance.destroy();
+            this.oActiveAudioInstance = null;
+        }
+    };
+
+    oMessenger.prototype.getVideoCall = function ({ lot, vc, user_id }) {
+        const _this = this;
+
+        if (!lot)
+            return false;
+
+        switch (vc) {
+            case 'start':
+                if (!$(_this.sJitsiMain).length && (!_oMessenger.isBlockVersion() || (_oMessenger.isBlockVersion() && _oMessenger.oSettings.lot === lot)))
+                    $.get('modules/?r=messenger/get_call_popup', { lot },
+                        function (oData) {
+                            if (!oData.code) {
+                                processJsonData(oData);
+                                _this.playSound('call', true);
+                                _this.broadcastMessage({
+                                    lot: lot,
+                                    addon: 'vc',
+                                    type: 'vc',
+                                    vc: 'got'
+                                });
+
+                                if (typeof (_this.aJitisActiveUsers[lot]) === 'undefined')
+                                    _this.aJitisActiveUsers[lot] = { owner: user_id, got: 0 };
+                                else
+                                    _this.aJitisActiveUsers[lot].owner = user_id;
+                            }
+                        }, 'json');
+                break;
+            case 'stop':
+                if (typeof (_this.aJitisActiveUsers[lot]) !== 'undefined') {
+                    if ($(`div[data-conferance='${lot}']`).length && _this.aJitisActiveUsers[lot].owner === user_id) {
+                        _this.onCloseCallPopup($(`div[data-conferance='${lot}']`), lot);
+                    }
+                }
+                break;
+            case 'got':
+                if (typeof (_this.aJitisActiveUsers[lot]) !== 'undefined')
+                    _this.aJitisActiveUsers[lot].got++;
+                else
+                    _this.aJitisActiveUsers[lot] = { owner: 0, got: 1 };
+
+                break;
+            case 'break':
+                if (typeof (_this.aJitisActiveUsers[lot]) !== 'undefined') {
+                    if (+_this.aJitisActiveUsers[lot].got)
+                        _this.aJitisActiveUsers[lot].got--;
+
+                    if (!_this.aJitisActiveUsers[lot].got && _this.aJitisActiveUsers[lot].owner === _this.oSettings.user_id) {
+                        _this.closeJitsi(_this.sJitsiMain, lot);
+                        _this.beep();
+                    }
+                }
+                break;
+            case 'join':
+                _this.stopActiveSound();
+                break;
+        }
+    };
+
+    /**
+     * Move lot's brief to the top of the left side when new message received or just update brief message
+     *@param object oObject lot's settings
+     */
+    oMessenger.prototype.upLotsPosition = function (oObject, bSilentMode = false) {
+        const _this = this,
+            { lot, addon, jot_id } = oObject,
+            { talksListItems, talksList, topItem, talkItem } = window.oMessengerSelectors.TALKS_LIST,
+            { jotMain } = window.oMessengerSelectors.JOT,
+            { msgContainer } = window.oMessengerSelectors.SYSTEM,
+            fBeep = (muted) => {
+                if ((typeof addon === 'undefined' || typeof addon === 'object') && !bSilentMode && !muted)
+                    $(_this).trigger(jQuery.Event('message'));
+            };
+
+
+        if (this.iThreadId)
+            return;
+
+        let oLot = $('[data-lot=' + lot + ']');
+        if (addon === 'remove_lot' && oLot.length) {
+            oLot.fadeOut().remove();
+            if ($(talksListItems).length)
+                $(talksListItems).first().click();
+
+            return;
+        }
+
+        if ((addon === 'delete' || addon === 'edit') && !(jot_id && $(`[data-id="${jot_id}"] ${jotMain}`).is(':last-child')))
+            return
+
+        $.get('modules/?r=messenger/update_lot_brief', { lot_id: lot },
+            function ({ html, code, muted, params }) {
+                if (+code)
+                    return;
+
+                if (typeof params !== 'undefined' && lot !== _this.oSettings.lot) {
+                    if (!bSilentMode)
+                        $(_this).updateMenuBubbles(lot, params);
+
+                    const { type, group_id } = params,
+                        { area_type } = _this.oSettings;
+
+                    if (type !== area_type && area_type !== 'inbox')
+                        return fBeep(muted);
+                    else if (area_type === 'groups' && type === 'groups' && +group_id !== +_this.oSettings.group_id)
+                        return fBeep(muted);
+                }
+
+                const sHtml = html.replace(new RegExp(_this.sJotUrl + '\\d+', 'i'), _t('_bx_messenger_repost_message')),
+                    oNewLot = $(sHtml).css('display', 'flex');
+
+                if ($(msgContainer, talksList).length)
+                    $(msgContainer, talksList).remove();
+
+                const sFunc = () => {
+                    const oUpdatedLot = $(oNewLot).bxMsgTime().fadeIn('slow');
+                    if ($(topItem).length)
+                        $(topItem)
+                            .after(oUpdatedLot);
+                    else
+                        $('> ul', talksList)
+                            .prepend(oUpdatedLot);
+
+                    if (+lot === +_this.oSettings.lot)
+                        _this.oMenu.toggleMenuItem(`${talkItem}[data-lot="${lot}"]`);
+
+                    _this.updatePageIcon();
+
+                };
+                const oLotItem = $(`[data-lot='${lot}']`);
+                if (oLotItem.length)
+                    oLotItem.fadeOut('slow', () => {
+                        oLotItem.remove();
+                        sFunc();
+                    });
+                else
+                    sFunc();
+
+                _this.setUsersStatuses(oLot);
+                fBeep(muted);
+
+            }, 'json');
+    };
+
+    oMessenger.prototype.notifyNewUnreadChats = function (iNewNumberOfUnreadChats) {
+        if (typeof window.glBxMessengerOnNotificationChange !== 'undefined' && window.glBxMessengerOnNotificationChange instanceof Array) {
+            for (let i = 0; i < window.glBxMessengerOnNotificationChange.length; i++)
+                if (typeof window.glBxMessengerOnNotificationChange[i] === "function")
+                    window.glBxMessengerOnNotificationChange[i](iNewNumberOfUnreadChats);
+        }
+    };
+
+    /**
+     * Show member's typing area when member is typing a message
+     *@param object oData profile info
+     */
+    oMessenger.prototype.showTyping = function ({ name, lot }) {
+        const _this = this,
+            sName = name ? name.toLowerCase() : '',
+            { infoArea, typingInfoArea } = window.oMessengerSelectors.HISTORY_INFO;
+
+        if (this.isActiveLot(lot)) {
+            if (!~this.aUsers.indexOf(sName))
+                this.aUsers.push(sName);
+
+            $(typingInfoArea).text(this.aUsers.join(','));
+            $(infoArea).fadeIn();
+        }
+
+        clearTimeout(this.iTimer);
+        this.iTimer = setTimeout(function () {
+            $(infoArea)
+                .fadeOut()
+                .find(typingInfoArea)
+                .html('');
+
+            _this.aUsers = [];
+
+        }, _this.iTypingUsersTitleHide);
+    };
+
+    oMessenger.prototype.onReconnecting = function (oData) {
+        const _this = this,
+            { infoArea, typingInfoArea, connectingArea } = window.oMessengerSelectors.HISTORY_INFO;
+
+        $(infoArea).fadeIn();
+        $(typingInfoArea).parent().hide();
+        $(connectingArea).show();
+        $(' > span', connectingArea).html('');
+
+        this.blockSendMessages(true);
+        clearInterval(this.iTimer);
+
+        this.iTimer = setInterval(function () {
+            let sHTML = $(' > span', connectingArea).html();
+            sHTML += '.';
+            $(' > span', connectingArea).html(sHTML);
+        }, 1000);
+    };
+
+    oMessenger.prototype.onReconnected = function (oData) {
+        const _this = this,
+            { infoArea, typingInfoArea, connectingArea } = window.oMessengerSelectors.HISTORY_INFO;
+
+        $(connectingArea).hide();
+        $(infoArea).fadeOut();
+        $(typingInfoArea).parent().show();
+
+        clearInterval(this.iTimer);
+        this.blockSendMessages(false);
+        this.updateJots({
+            action: 'msg'
+        });
+
+        const { lot, area_type, group_id } = this.oSettings;
+        this.loadTalksListByParam({ group: area_type, id: group_id }, () => {
+            if (lot) {
+                const { talkItem } = window.oMessengerSelectors.TALKS_LIST;
+                _this.selectLotEmit($(`[data-lot="${lot}"]${talkItem}`));
+            }
+        });
+    };
+
+    oMessenger.prototype.onReconnectFailed = function (oData) {
+        const { connectingArea, connectionFailedArea } = window.oMessengerSelectors.HISTORY_INFO;
+        $(connectingArea).hide();
+        $(connectionFailedArea).fadeIn();
+
+        clearInterval(this.iTimer);
+    };
+
+    /**
+     * Check if specified lot is currently active
+     *@param int iId profile id
+     *@return boolean
+     */
+    oMessenger.prototype.isActiveLot = function (iId, sArea = false) {
+        const { lot, area_type } = this.oSettings;
+
+        return iId && +lot === +iId && ((sArea !== false && sArea === area_type) || sArea === false);
+    }
+
+    oMessenger.prototype.isViewInBottomPosition = function () {
+        const { talkBlock } = window.oMessengerSelectors.HISTORY,
+            oTalkBlock = $(talkBlock),
+            iHeight = oTalkBlock.prop('scrollHeight'),
+            iClient = oTalkBlock.prop('clientHeight'),
+            _this = this;
+
+        if (iHeight > iClient) {
+            const iCurrentScrollHeight = iHeight - iClient;
+            const iCurrentScrollPos = oTalkBlock.scrollTop();
+
+            if ((iCurrentScrollHeight - iCurrentScrollPos) > iClient)
+                return false;
+        }
+
+        return true;
+    }
+
+    oMessenger.prototype.scrollIntoView = function (oObject, sBlock, fCallback) {
+        if (this.isBlockVersion() || typeof oObject === 'undefined' || typeof oObject.scrollIntoView !== 'function')
+            return false;
+
+        oObject.scrollIntoView({ behavior: 'auto', block: sBlock });
+        if (typeof fCallback === 'function')
+            setTimeout(() => fCallback(), 200);
+    }
+
+    /**
+     * Correct scroll position in history area depends on loaded messages (old history or new just received)
+     *@param string sPosition position name
+     *@param string sEff name of the effect for load
+     *@param object oObject any history item near which to place the scroll
+     *@param function fCallback executes when scrolling complete
+     */
+    oMessenger.prototype.updateScrollPosition = function (sPosition, sEff, mixedObject, fCallback) {
+        const sEffect = sEff,
+            { talkBlock } = window.oMessengerSelectors.HISTORY,
+            { talkListJotSelector } = window.oMessengerSelectors.JOT,
+            iHeight = $(talkBlock).prop('scrollHeight');
+
+        let iPosition = 0;
+        switch (sPosition) {
+            case 'top':
+                const { pos, object } = mixedObject || {};
+                iPosition = pos ? pos : 0;
+
+                if (this.scrollIntoView(object && object[0], 'start', fCallback) !== false)
+                    return;
+
+                break;
+            case 'bottom':
+                const oLast = (mixedObject && mixedObject[0]) || $(talkListJotSelector).last()[0];
+                if (!sEff && this.scrollIntoView(oLast, 'end', fCallback) !== false)
+                    return;
+
+                iPosition = iHeight;
+                break;
+            case 'position':
+                iPosition = typeof mixedObject !== 'undefined' ? mixedObject.position().top : 0;
+                break;
+            case 'center':
+                const oJot = mixedObject && mixedObject[0];
+                if (this.scrollIntoView(oJot, 'center', fCallback) !== false)
+                    return;
+
+                iPosition = mixedObject.position().top - $(talkBlock).prop('clientHeight') / 2;
+                break;
+            case 'freeze':
+                iPosition = $(talkBlock).scrollTop();
+                break;
+        }
+
+        if (sEffect !== 'slow') {
+            setTimeout(() => {
+                $(talkBlock).scrollTop(iPosition);
+                if (typeof fCallback === 'function')
+                    fCallback();
+            }, 0);
+        } else
+            $(talkBlock).animate({
+                scrollTop: iPosition,
+            }, sEffect === 'slow' ? this.iScrollDownSpeed : 0,
+                typeof fCallback === 'function' ? fCallback : function () {
+                });
+    }
+
+    /**
+     * Sound when message received
+     */
+    oMessenger.prototype.beep = function () {
+        if (!document.hasFocus()) {
+            if (!this.iMuted)
+                this.playSound('incomingMessage');
+
+            this.updatePageIcon(true);
+        }
+    };
+
+    oMessenger.prototype.getSendAreaAttachmentsIds = function (sSelector = '', bClean = true) {
+        const oObject = { length: 0 },
+            { attachmentArea } = window.oMessengerSelectors.TEXT_AREA;
+
+        $(`${sSelector}${attachmentArea}`)
+            .children()
+            .each(function () {
+                const oPicture = $('picture', $(this)),
+                    iId = $(oPicture).data('id'),
+                    sType = $(oPicture).data('type');
+
+                if (iId && sType) {
+                    if (typeof oObject[sType] === 'undefined' || !Array.isArray(oObject[sType]))
+                        oObject[sType] = [];
+
+                    oObject[sType].push(iId);
+                    oObject.length++;
+                }
+            });
+
+        if (bClean === true)
+            $(`${sSelector}${attachmentArea}`).html('');
+
+        return oObject;
+    };
+
+    /**
+     * Update history area, occurs when new messages are received(move scroll to the very bottom) or member loads the history(move scroll to the very top)
+     *@param object oAction info about an action
+     */
+    oMessenger.prototype.updateJots = function (oAction, bSilentMode = false) {
+        const _this = this,
+            { talkBlock, conversationBody } = window.oMessengerSelectors.HISTORY,
+            { talkListJotSelector, jotMain, jotMessage, jotMessageView } = window.oMessengerSelectors.JOT,
+            { dateIntervalsSelector } = window.oMessengerSelectors.DATE_SEPARATOR,
+            { attachmentArea } = window.oMessengerSelectors.ATTACHMENTS,
+            { addon, position, action, last_viewed_jot, callback, jot_id, user_id } = oAction;
+
+        let sAction = typeof addon === 'string' ? addon : (action !== 'msg' ? action : 'new'),
+            iRequestJot = 0,
+            iJotId = 0;
+
+        const sPosition = position || (sAction === 'new' ? 'bottom' : 'position'),
+            oObjects = $(talkListJotSelector);
+
+        if (this.iThreadId)
+            return;
+
+        switch (sAction) {
+            case 'update_attachment':
+                if (+_this.oSettings.user_id !== +user_id)
+                    _this.attacheFiles(jot_id);
+                return;
+            case 'check_viewed':
+            case 'reaction':
+            case 'delete':
+            case 'edit':
+            case 'vc':
+                iJotId = jot_id || 0;
+                break;
+            case 'clear':
+                return $(conversationBody).html('');
+            case 'prev':
+                iJotId = oObjects
+                    .first()
+                    .data('id');
+
+                break;
+            case 'new':
+                iRequestJot = (typeof addon === 'object' && typeof addon.jot_id !== 'undefined' ? addon.jot_id : 0);
+                if (!oObjects.length)
+                    sAction = 'all';
+            default:
+                iJotId = oObjects
+                    .last()
+                    .data('id');
+        }
+
+        const iLotId = _this.oSettings.lot; // additional check for case when ajax request is not finished yet but another talk is selected
+
+        if (sAction === 'prev' || (sAction === 'new' && _this.iSelectedJot)) {
+            bx_loading($(`[data-id="${iJotId}"] ${jotMain}`), true);
+        }
+
+        const bIsMobileTalksList = oMUtils.isMobile() && _this.oMenu.isHistoryColActive();
+        $.post('modules/?r=messenger/update',
+            {
+                url: this.oSettings.url,
+                jot: iJotId,
+                lot: this.oSettings.lot,
+                area_type: this.oSettings.area_type,
+                load: sAction,
+                req_jot: iRequestJot,
+                focus: +(bIsMobileTalksList ? false : document.hasFocus()),
+                last_viewed_jot
+            },
+            function ({ html, unread_jots, code, last_unread_jot, allow_attach, reload }) {
+                bx_loading($(`[data-id="${iJotId}"] ${jotMain}`), false);
+                const oList = $(conversationBody);
+
+                if (iLotId !== _this.oSettings.lot)
+                    return;
+
+                if (+reload) {
+                    window.location.reload();
+                    return;
+                }
+
+                if (!parseInt(code)) {
+                    if (iJotId === undefined)
+                        oList.html('');
+
+                    switch (sAction) {
+                        case 'all':
+                        case 'new':
+                            _this.updateCounters(unread_jots, true);
+
+                            if (last_unread_jot && (!_this.iLastUnreadJot || _this.iLastUnreadJot < last_unread_jot))
+                                _this.iLastUnreadJot = last_unread_jot;
+
+                            _this.bAllowAttachMessages = allow_attach;
+                            if (!html.length)
+                                return;
+
+                            const oContent = $(html).initJotIcons().initAccordion(),
+                                aContent = [];
+
+                            oContent
+                                .filter(jotMain)
+                                .each(
+                                    function () {
+                                        if ($('div[data-id="' + $(this).data('id') + '"]', oList).length)
+                                            return;
+
+                                        $(`${jotMessageView} img`, this)
+                                            .each(function () {
+                                                $(`${jotMain} ${jotMessageView} img[data-viewer-id="${$(this).data('viewer-id')}"]`).remove()
+                                            })
+                                            .end()
+                                            .closest(jotMessageView)
+                                            .fadeIn();
+
+                                        aContent.push($(this));
+                                    })
+                                .end();
+
+                            if (aContent.length) {
+                                $(oList).append(oContent);
+                                oContent.bxProcessHtml();
+                                $(oList)
+                                    .waitForImages(() => _this.updateScrollPosition(sPosition ? sPosition : 'bottom', 'fast', $(conversationBody).last()));
+
+
+                                if ((_this.isBlockVersion() || (oMUtils.isMobile() && _oMessenger.oMenu.isHistoryColActive())) && !bSilentMode)  /* play sound for jots only on mobile devices when chat area is active */
+                                    $(_this).trigger(jQuery.Event('message'));
+
+                                if ($(talkListJotSelector).length > _this.iMaxHistory && iRequestJot) {
+                                    let iCountToRemove = $(talkListJotSelector).length - _this.iMaxHistory;
+                                    while (iCountToRemove-- > 0) {
+                                        $(talkListJotSelector).first().remove();
+                                    }
+                                }
+                            }
+
+                            break;
+                        case 'prev':
+                            if (!html.length)
+                                break;
+
+                            const oPrevContent = $(html)
+                                .initJotIcons()
+                                .initAccordion()
+                                .filter(jotMain)
+                                .each(function () {
+                                    $(`${jotMessageView} img`, this)
+                                        .each(function () {
+                                            if ($(`${jotMain} ${jotMessageView} img[data-viewer-id="${$(this).data('viewer-id')}"]`).length)
+                                                $(this).remove();
+                                        })
+                                        .end()
+                                        .closest(jotMessageView)
+                                        .fadeIn();
+                                })
+                                .end();
+
+                            oList
+                                .prepend(oPrevContent)
+                                .bxProcessHtml()
+                                .waitForImages(oParent => {
+                                        const iId = oObjects.first().data('id');
+                                        if (+iId)
+                                            setTimeout(() => {
+                                                const iTop = $(`[data-id="${iId}"]${jotMain}`, oParent).position().top || 0;
+                                                _this.updateScrollPosition('top', 'fast', {
+                                                    pos: iTop,
+                                                    object: oObjects.first()
+                                                });
+                                            }, 0);
+                                    });
+                            break;
+                        case 'edit':
+                        case 'vc':
+                            if (!$('div[data-id="' + iJotId + '"]').length)
+                                oList
+                                    .append(html);
+                            else
+                                $('div[data-id="' + iJotId + '"] ' + jotMessage, oList)
+                                    .html(html)
+                                    .parent()
+                                    .bxMsgTime();// don't update attachment for message and don't broadcast as new message
+                            return;
+                        case 'delete':
+                            const onRemove = function () {
+                                if ($(this).prev().hasClass(dateIntervalsSelector.substr(1)) && !$(this).next().length)
+                                    $(this).prev().remove();
+
+                                $(this).remove();
+
+                                _this.updateScrollPosition('bottom');
+                            };
+
+                            if (html.length)
+                                $('div[data-id="' + iJotId + '"] ' + jotMessage, oList)
+                                    .html(html)
+                                    .parent()
+                                    .linkify(true, true)
+                                    .find(attachmentArea)
+                                    .fadeOut('slow', onRemove);
+                            /*  if nothing returns, then remove html code completely */
+                            else {
+                                $('div[data-id="' + iJotId + '"]', oList)
+                                    .fadeOut('slow', onRemove);
+                            }
+                            break;
+                        case 'check_viewed':
+                            const aUsers = $(html).filter('img');
+
+                            if (aUsers.length) {
+                                aUsers.each(function () {
+                                    let iProfileId = $(this).data('viewer-id');
+                                    $(`${jotMain} ${jotMessageView} img[data-viewer-id="${iProfileId}"]`).remove();
+                                });
+
+                                return $(`div[data-id="${iJotId}"] ${jotMessageView}`, oList)
+                                    .html(html)
+                                    .fadeIn();
+                            }
+                            break;
+                        case 'reaction':
+                            let iOriginalCount = 0;
+
+                            $(html)
+                                .each(function () {
+                                    iOriginalCount += $(this).data('count');
+                                });
+
+                            $(`div[data-id="${iJotId}"] ${_this.sReactionsArea} > span`, oList)
+                                .each(function () {
+                                    iOriginalCount -= $(this).data('count');
+                                });
+
+                            if (iOriginalCount > 0)
+                                _this.playSound('reaction');
+
+                            const oReaction = $(`div[data-id="${iJotId}"] ${_this.sReactionsArea}`, oList);
+
+                            $('> span', oReaction).remove();
+
+                            oReaction
+                                .prepend(html);
+
+                            if (html.length)
+                                $(_this.sReactionMenu, oReaction).fadeIn();
+                            else
+                                $(_this.sReactionMenu, oReaction).fadeOut();
+                    }
+
+                    oList
+                        .find(jotMain + ':hidden')
+                        .fadeIn(
+                            function () {
+                                $(this).css('display', 'flex').initJotIcons();
+                            })
+                        .bxMsgTime();
+
+                    if (typeof callback === 'function')
+                        callback();
+                }
+
+                if (sAction === 'prev')
+                    bx_loading($(talkBlock), false);
+
+            }, 'json');
+    };
+
+    /**
+     * Init user selector when create or edit participants list of the lot
+     *@param boolean bMode if used for edit or to create new lot
+     */
+    oMessenger.prototype.onJotAddReaction = function (oEmoji, iJotId) {
+        const { id } = oEmoji,
+            _this = this,
+            { conversationBody } = window.oMessengerSelectors.HISTORY;
+
+        if (id && iJotId) {
+            const oReactionsArea = $(`div[data-id="${iJotId}"] ${this.sReactionsArea}`, conversationBody),
+                oReaction = $(`span[data-emoji="${id}"]`, oReactionsArea);
+
+            if (!oReaction.length) {
+                oReactionsArea.prepend(
+                    this.sReactionTemplate
+                        .replace(/__emoji_id__/g, id)
+                        .replace(/__parts__/g, this.oSettings.user_id)
+                        .replace(/__count__/g, 1)
+                        .replace(/__value__/g, oEmoji.native)
+                ).fadeIn(() => _this.playSound('reaction'));
+            } else {
+                let iCount = +$(oReaction).data('count'),
+                    aParts = $(oReaction).data('part').toString().split(',');
+
+                if (!~aParts.indexOf(this.oSettings.user_id + '')) {
+                    iCount++;
+                    aParts.push(this.oSettings.user_id);
+                    $(oReaction).data('part', aParts.join(','));
+                    $(oReaction).data('count', iCount);
+
+                    if ($('> span', oReaction).length)
+                        $('> span', oReaction).text(iCount);
+
+                    _this.playSound('reaction');
+                }
+            }
+
+            $.post('modules/?r=messenger/jot_reaction', { jot: iJotId, emoji: oEmoji, action: 'add' }, function (oData) {
+
+                if ($(_this.sReactionItem, oReactionsArea).length === 1)
+                    $(_this.sReactionMenu, oReactionsArea).fadeIn();
+
+                if (+!oData.code)
+                    _this.broadcastMessage({
+                        jot_id: iJotId,
+                        addon: 'reaction'
+                    });
+
+            }, 'json');
+        }
+    };
+
+    oMessenger.prototype.onTextAreaAddEmoji = function (oEmoji) {
+        this.oEditor.addToCurrentPosition(oEmoji.native);
+    };
+
+    /**
+     * Run Jitsi video chat
+     *@param string sId of the image
+     */
+    oMessenger.prototype.startVideoCall = function (oEl, iLotId, sRoom, oOptions = {}) {
+        const _this = this,
+
+            fDesktopCall = () => {
+                let oParams = Object.assign({}, oOptions, !iLotId && {
+                    url: encodeURIComponent(_this.oSettings.url),
+                    title: _this.oSettings.title
+                });
+                if (typeof oParams.callback !== 'undefined')
+                    delete oParams['callback'];
+
+                if (oEl)
+                    bx_loading_btn($(oEl), true);
+
+                $(window).dolPopupAjax({
+                    url: bx_append_url_params(`modules/?r=messenger/get_jitsi_conference_form/${iLotId}`, oParams),
+                    id: {
+                        force: true,
+                        value: _this.sJitsiVideo.substr(1)
+                    },
+                    fog: false,
+                    removeOnClose: true,
+                    closeOnOuterClick: false,
+                    onBeforeShow: () => oOptions && typeof oOptions.callback === 'function' && oOptions.callback(),
+                    onShow: () => oEl && bx_loading_btn($(oEl), false),
+                });
+            },
+
+            fMobileCall = () => {
+                if ('undefined' !== typeof (window.ReactNativeWebView)) {
+
+                    if (typeof window.glBxVideoCallJoined === 'undefined') {
+                        window.glBxVideoCallJoined = [];
+                    }
+
+                    window.glBxVideoCallJoined.push(function (e) {
+                        if (oEl)
+                            bx_loading_btn($(oEl), true);
+
+                        if (oOptions && typeof oOptions.callback === 'function')
+                            oOptions.callback();
+
+                        $.get('modules/?r=messenger/create_jitsi_video_conference/', { lot_id: iLotId }, function (oData) {
+                            const { message, opened, code, jot_id } = oData;
+                            bx_loading_btn($(oEl), false);
+
+                            if (+code === 1) {
+                                bx_alert(message);
+                                return;
+                            }
+
+                            if (typeof opened !== 'undefined' && Array.isArray(opened))
+                                if (Array.isArray(opened))
+                                    opened.map(jot_id => _this.updateJots({
+                                        action: 'vc',
+                                        jot_id
+                                    }));
+
+                            if (iLotId) {
+                                const oInfo = { type: 'vc', vc: 'start' };
+
+                                if (jot_id && !oData.new) {
+                                    oInfo.jot_id = jot_id;
+                                    oInfo.vc = 'join';
+                                } else
+                                    _this.playSound('call', true);
+
+                                _this.broadcastMessage(oInfo);
+                                _this.updateJots(oInfo);
+                            }
+
+                            if (typeof window.glBxVideoCallTerminated === 'undefined') {
+                                window.glBxVideoCallTerminated = [];
+                            }
+
+                            window.glBxVideoCallTerminated.push(function (e) {
+                                _this.stopActiveSound();
+                                $.get('modules/?r=messenger/stop_jvc/', { lot_id: iLotId }, (oData) => {
+                                    const oInfo = {
+                                        jot_id: jot_id,
+                                        addon: 'vc',
+                                        type: 'vc',
+                                        vc: 'stop'
+                                    };
+
+                                    if (+oData.code && oData.msg)
+                                        bx_alert(oData.msg);
+
+                                    _this.broadcastMessage(oInfo);
+                                    _this.updateJots(oInfo);
+
+                                }, 'json');
+                            });
+                        },
+                            'json');
+                    });
+
+                    const oVideoParams = { uri: (_this.sJitsiServerUrl ? `${_this.sJitsiServerUrl}/${sRoom}` : sRoom) };
+                    if (typeof oOptions.startAudioOnly !== 'undefined' && oOptions.startAudioOnly === true)
+                        oVideoParams['audio'] = true;
+
+                    // call mobile video call
+                    if (typeof bx_mobile_apps_post_message === 'function')
+                        bx_mobile_apps_post_message({ video_call_start: oVideoParams });
+                }
+                ;
+
+                return false;
+            };
+
+        if (oMUtils.isMobileDevice()) {
+            if (!oMUtils.isUnaMobileApp())
+                bx_confirm(_t('_bx_messenger_jitsi_mobile_warning'), fDesktopCall, fMobileCall);
+            else
+                fMobileCall();
+        } else
+            fDesktopCall();
+    };
+
+    oMessenger.prototype.onCloseCallPopup = function (oEl, iLotId, sType = 'break', bClose = true) {
+        const oParams = {
+            type: 'vc',
+            vc: sType,
+            lot: iLotId
+        },
+            { jotMain } = window.oMessengerSelectors.JOT,
+            iJot = $(this.sJitsiJoinButton)
+                .last()
+                .closest(jotMain)
+                .data('id');
+
+        this.stopActiveSound();
+        this.broadcastMessage(oParams);
+
+        if (bClose) {
+            if (iJot)
+                this.updateJots(Object.assign(oParams, {
+                    action: 'vc',
+                    jot_id: iJot
+                }));
+
+            if (typeof (this.aJitisActiveUsers[iLotId]) !== 'undefined')
+                delete this.aJitisActiveUsers[iLotId];
+        }
+
+        $(oEl)
+            .closest('.bx-popup-active:visible')
+            .dolPopupHide({
+                removeOnClose: true
+            });
+    };
+
+    oMessenger.prototype.closeJitsi = function (oElement, iLotID) {
+        const oJitsi = this.oJitsi,
+            _this = this,
+            { jotMain } = window.oMessengerSelectors.JOT,
+            fClose = function () {
+                const jotId = $(_this.sJitsiJoinButton)
+                    .last()
+                    .closest(jotMain)
+                    .data('id');
+
+                sFunc = () => {
+                    const oInfo = {
+                        jot_id: jotId,
+                        addon: 'vc',
+                        type: 'vc',
+                        vc: 'stop'
+                    };
+                    _this.broadcastMessage(oInfo);
+                    _this.updateJots(oInfo);
+
+                    if (typeof (_this.aJitisActiveUsers[iLotID]) !== 'undefined')
+                        delete _this.aJitisActiveUsers[iLotID];
+                };
+
+                _this.stopActiveSound();
+                $.get('modules/?r=messenger/stop_jvc/', { lot_id: iLotID || _this.oSettings.lot }, (oData) => {
+                    if (+oData.code && oData.msg)
+                        bx_alert(oData.msg);
+                    sFunc();
+                }, 'json');
+
+                if (oJitsi) {
+                    oJitsi.dispose();
+                    _this.oJitsi = null;
+                }
+            },
+            oPopupWindow = oElement && $(oElement).closest(this.sActivePopup);
+
+        if (oPopupWindow && oPopupWindow.length)
+            oPopupWindow.dolPopupHide({
+                removeOnClose: true,
+                onBeforeHide: fClose,
+            });
+        else
+            fClose();
+
+        $(window).unbind('beforeunload');
+    };
+
+    oMessenger.prototype.setPositionOnSelectedJot = function (fCallback) {
+        const _this = this,
+            { conversationBody, mainScrollArea } = window.oMessengerSelectors.HISTORY,
+            { selectedJot } = window.oMessengerSelectors.JOT;
+
+        if ($(selectedJot, conversationBody).length)
+            _this.updateScrollPosition('center', 'fast', $(selectedJot, conversationBody),
+                function () {
+                    $(mainScrollArea).fadeIn('slow', () => {
+                        if (typeof fCallback === 'function')
+                            fCallback();
+                    });
+                });
+        else {
+            _this.updateScrollPosition('bottom', undefined, undefined, () => {
+                if (typeof fCallback === 'function')
+                    fCallback();
+            });
+        }
+    }
+
+    oMessenger.prototype.loadTalksList = function (fCallback, bUpdate = true) {
+        const _this = this,
+            { talksList, talksListItems } = window.oMessengerSelectors.TALKS_LIST;
+
+        let oParams = Object.create(null);
+        let oLotObject = $(talksList);
+
+        if (!bUpdate) {
+            oLotObject = $(talksListItems).last();
+            oParams = { count: $(talksListItems).length, group: _this.oSettings.area_type };
+        }
+
+        if (_this.iSelectedJot || _this.iSelectedPersonToTalk)
+            oParams.exclude_convo = _this.oSettings.lot;
+
+        bx_loading(oLotObject, true);
+        $.post('modules/?r=messenger/get_talks_list', oParams, function ({ code, html, reload, title }) {
+            bx_loading(oLotObject, false);
+
+            if (+reload) {
+                window.location.reload();
+                return;
+            }
+
+            if (!+code) {
+                $('> ul', talksList)
+                [bUpdate ? 'html' : 'append']($(html).bxMsgTime());
+            } else
+                $(talksListItems).first().removeClass('hidden');
+
+            if (title && $(_this.sInboxTitle).length)
+                $(_this.sInboxTitle).text(title);
+
+            if (typeof fCallback === 'function')
+                fCallback(+code);
+        },
+            'json');
+    }
+
+    oMessenger.prototype.getThreadsReplies = function (iReplyId) {
+        const _this = this,
+            { conversationBody } = window.oMessengerSelectors.HISTORY,
+            { jotMessage } = window.oMessengerSelectors.JOT,
+            oObject = $(`div[data-id="${iReplyId}"]`, conversationBody);
+
+        if (!iReplyId || !oObject.length)
+            return;
+
+        $.post('modules/?r=messenger/get_thread_replies', { jot_id: iReplyId }, function ({ code, html }) {
+            if (!+code) {
+                $('.bx-messenger-jot-replies', oObject).remove();
+                $(oObject).find(jotMessage).after(html);
+            }
+        },
+            'json');
+    }
+
+    oMessenger.prototype.loadTalksListByParam = function (mixedData, fCallback) {
+        const _this = this,
+            { group, id, lot } = mixedData || {},
+            { talksList, topItem, inboxAreaTitle } = window.oMessengerSelectors.TALKS_LIST,
+            { messengerBlock } = window.oMessengerSelectors.TALK_BLOCK,
+            { mainTalkBlock, conversationBody } = window.oMessengerSelectors.HISTORY,
+            { blockContainer, blockHeader } = window.oMessengerSelectors.SYSTEM,
+            sTopSelector = `ul > li:not(${topItem}), ul > div`;
+
+        _this.oSettings.area_type = group;
+        _this.oSettings.group_id = id;
+        if (group === 'groups' && +id) {
+            _this.oSettings.group_id = id;
+        }
+
+        $(sTopSelector, talksList)
+            .remove();
+
+        bx_loading($(talksList), true);
+        $.post('modules/?r=messenger/get_talks_list', { group, id, lot }, function ({ code, html, title }) {
+            bx_loading($(talksList), false);
+            if (html.length) {
+                $('> ul', talksList)
+                    .append($(code ? '<li>' + html + '</li>' : html).bxMsgTime());
+            }
+
+            $(talksList).initLazyLoading((oObject, bFlag) => _this.loadTalksList(oObject, bFlag));
+            if (title && $(inboxAreaTitle).length)
+                $(inboxAreaTitle).text(title);
+
+            const oMessengerBlock = $(messengerBlock).length ? messengerBlock : $(mainTalkBlock).closest(blockContainer);
+            if (code) {
+                $(oMessengerBlock)
+                    .find(blockHeader)
+                    .html('')
+                    .end()
+                    .find(conversationBody)
+                    .html('')
+                    .end()
+                    .find(_this.sTextArea)
+                    .html('');
+
+                _this.oSettings.lot = 0;
+                _this.oSettings.group_id = 0;
+            }
+
+            if (typeof fCallback === 'function')
+                fCallback(+code);
+        },
+            'json');
+    }
+
+    /**
+     * Init settings, occurs when member opens the main messenger page
+     * @param fCallback
+     */
+    oMessenger.prototype.initMessengerPage = function (fCallback) {
+        const _this = this,
+            { conversationBody } = window.oMessengerSelectors.HISTORY,
+            { talkItem, talksList } = window.oMessengerSelectors.TALKS_LIST,
+            { searchUsersInput } = window.oMessengerSelectors.CREATE_TALK,
+            { bxMain } = window.oMessengerSelectors.SYSTEM;
+
+        if (typeof oMessengerMemberStatus !== 'undefined') {
+            oMessengerMemberStatus.init(function (iStatus) {
+                _this.iStatus = iStatus;
+                if (typeof _this.oRTWSF !== "undefined")
+                    _this.oRTWSF.updateStatus({
+                        user_id: _this.oSettings.user_id,
+                        status: iStatus,
+                    });
+            });
+        }
+
+        let iTimeout = null;
+        $(window).on('touchmove resize', function ({ type }) { //resize
+            _this.updateSendAreaButtons();
+            clearTimeout(iTimeout);
+            iTimeout = setTimeout(() => {
+                oNavMenu.onResize();
+                _this.selectLotEmit($(`[data-lot="${_this.oSettings.lot}"]${talkItem}`));
+                $(conversationBody).waitForImages(() => {
+                    if (type === 'resize')
+                        _this.setPositionOnSelectedJot(fCallback);
+                });
+
+                _this.setScrollBarWidth();
+
+                if ($(searchUsersInput).length)
+                    $(searchUsersInput).focus();
+                else if (_this.oEditor && !oMUtils.isMobile())
+                    _this.oEditor.focus();
+
+                if (oMUtils.isMobile() && $(bxMain).length)
+                    $(bxMain).height($(window).height());
+
+            }, 200);
+        });
+
+        $(window).resize();
+
+        _this.setPositionOnSelectedJot(fCallback);
+
+        $(talksList).initLazyLoading((oObject, bFlag) => _this.loadTalksList(oObject, bFlag));
+
+        _this.updatePageIcon();
+
+        // this module class is missed when you open messenger page using not direct menu link.
+        $('body').addClass("bx-page-messenger");
+    };
+
+    /**
+     * Returns object with public methods
+     */
+    return {
+        /**
+         * Init main Lot's settings and object to work with (settings, real-time frame work, page builder and etc...)
+         *@param object oOptions options
+         */
+        init: function (oOptions) {
+            const _this = this;
+            if (_oMessenger != null)
+                return true;
+
+            _oMessenger = new oMessenger(oOptions);
+
+            const { inputArea, sendAreaActionsButtons } = window.oMessengerSelectors.TEXT_AREA,
+                { talkItem, talksListItems } = window.oMessengerSelectors.TALKS_LIST,
+                { infoArea, typingInfoArea, connectionFailedArea } = window.oMessengerSelectors.HISTORY_INFO,
+                { talkBlock } = window.oMessengerSelectors.HISTORY,
+                { messengerColumns } = window.oMessengerSelectors.MAIN_PAGE,
+                { searchUsersInput } = window.oMessengerSelectors.CREATE_TALK;
+
+            if (createjs) {
+                createjs.Sound.registerSound(_oMessenger.incomingMessage, 'incomingMessage');
+                createjs.Sound.registerSound(_oMessenger.reaction, 'reaction');
+                createjs.Sound.registerSound(_oMessenger.call, 'call');
+            }
+
+            /* Init sockets settings begin*/
+            if (typeof _oMessenger.oRTWSF !== 'undefined' && _oMessenger.oRTWSF.isInitialized()) {
+
+                $(window).on('beforeunload', function () {
+                    if (_oMessenger.oRTWSF !== undefined)
+                        _oMessenger.oRTWSF.end({
+                            user_id: oOptions.user_id
+                        });
+                });
+
+                _oMessenger.oRTWSF.onTyping = function (oData) {
+                    _this.onTyping(oData);
+                };
+
+                _oMessenger.oRTWSF.onMessage = function (oData) {
+                    _this.onMessage(oData);
+                };
+
+                _oMessenger.oRTWSF.onStatusUpdate = function (oData) {
+                    _this.onStatusUpdate(oData);
+                };
+
+                _oMessenger.oRTWSF.onServerResponse = function (oData) {
+                    _this.onServerResponse(oData);
+                };
+
+                _oMessenger.oRTWSF.onReconnecting = function (oData) {
+                    _oMessenger.onReconnecting(oData);
+                };
+
+                _oMessenger.oRTWSF.onReconnected = function (oData) {
+                    _oMessenger.onReconnected(oData);
+                };
+
+                _oMessenger.oRTWSF.onReconnectFailed = function (oData) {
+                    _oMessenger.onReconnectFailed(oData);
+                };
+
+                _oMessenger.oRTWSF.getSettings = function () {
+                    return $.extend({ status: _oMessenger.iStatus }, _oMessenger.oSettings);
+                };
+
+
+                _oMessenger.oRTWSF.onDestroy = function (oData) {
+                    $(infoArea).fadeIn();
+                    $(typingInfoArea).parent().hide();
+                    $(connectionFailedArea).fadeIn();
+                    _oMessenger.oRTWSF.end();
+                    _oMessenger.oRTWSF = undefined;
+                };
+
+            } else {
+                console.log('Real-time frameworks was not initialized');
+                const { serverErrorArea } = window.oMessengerSelectors.HISTORY_INFO;
+                $(infoArea).fadeIn();
+                $(typingInfoArea).parent().hide();
+                $(serverErrorArea).show();
+            }
+
+            /* Init sockets settings end */
+
+            // init browser storage
+            if (typeof oMessengerStorage !== 'undefined' && !_oMessenger.oStorage) {
+                _oMessenger.oStorage = new oMessengerStorage();
+            }
+
+            // init text area
+            _oMessenger.initTextArea();
+            // init default talks params
+            const oInitParams = {
+                lot: oOptions.lot,
+                jot: oOptions.jot_id,
+                last_unread_jot: oOptions.last_unread_jot,
+                unread_jots: oOptions.unread_jots,
+                allow_attach: oOptions.allow_attach
+            };
+
+            _oMessenger.updateLotSettings(oInitParams);
+            _oMessenger.initScrollArea();
+
+            if (!_oMessenger.isBlockVersion())
+                _oMessenger.initMessengerPage();
+            else {
+                $(document).ready(() => _oMessenger.setPositionOnSelectedJot(() => _oMessenger.setScrollBarWidth()));
+                $(window).on('resize', (e) => _oMessenger.setScrollBarWidth());
+            }
+
+            // find the all intervals in history
+            $(talkBlock)
+                .bxMsgTime();
+
+            $(talksListItems)
+                .bxMsgTime();
+
+            // init menu
+            if (!_oMessenger.oMenu && typeof window.oNavMenu !== 'undefined') {
+                _oMessenger.oMenu = window.oNavMenu;
+                _oMessenger.oMenu.setUniqueMode(_oMessenger.bUniqueMode);
+                _oMessenger.oMenu.toggleMenuItem(`${talkItem}[data-lot="${oOptions.lot}"]`);
+            }
+
+            // attach on ESC button return from create talk area
+            $(messengerColumns).on('keydown touchend mousedown', function (e) {
+                const { type, keyCode, target } = e;
+
+                if (+keyCode === 27 && $(target).prop('id') === searchUsersInput.substr(1)) {
+                    return history.back();
+                }
+
+                if ((type === 'touchend' || type === 'mousedown') && !$(target).closest(`${inputArea}, ${sendAreaActionsButtons}`).length) {
+                    if (_oMessenger.oEditor && oMUtils.isMobile())
+                        _oMessenger.oEditor.blur();
+                }
+                ;
+            });
+
+            $(window).on('focus', () => {
+                _oMessenger.updatePageIcon();
+                _oMessenger.broadcastView();
+            });
+
+            //remove all edit jot areas on their lost focus
+            $(document).on('mouseup', function (oEvent) {
+                _oMessenger.removeEditArea(oEvent);
+            })
+                .on('click touchstart', (oEvent) => _oMessenger.onOuterClick(oEvent));
+
+            _oMessenger.editOnClick();
+            if ((+oOptions.selected_profile || (+oOptions.jot_id && _oMessenger.bByUrl)) && oMUtils.isMobile())
+                _oMessenger.oMenu.showHistoryPanel();
+
+            _oMessenger.oHistory.pushState({
+                action: 'init',
+                lot: oOptions.lot,
+                jot: oOptions.jot_id,
+                area: oOptions.area_type
+            }, null);
+            _oMessenger.showWelcomeMessage(oOptions.welcome_message);
+
+            $(_oMessenger).initMenuBubbles(oOptions.messages);
+        },
+
+        /**
+         * Init Lot settings only (occurs when member selects any lot from lots list)
+         *@param object oOptions options
+         */
+        initLotSettings: function (oOptions) {
+            _oMessenger.initLotSettings(oOptions);
+        },
+
+        initTextArea: function () {
+            _oMessenger.initTextArea();
+        },
+
+        initFilesUploader: function () {
+            _oMessenger.initFilesUploader();
+        },
+
+        loadTalk: function (iLotId, iJotId = undefined) {
+            if (~_oMessenger.aLoadingRequestsPool.indexOf(iLotId))
+                return;
+
+            const iLength = _oMessenger.aLoadingRequestsPool.length;
+            const fLoading = () => {
+                const lot = _oMessenger.aLoadingRequestsPool[0];
+
+                if (+lot) {
+                    _oMessenger
+                        .loadTalk(lot, iJotId)
+                        .done(() => {
+                            if (typeof _oMessenger.oHistory.pushState === 'function')
+                                _oMessenger.oHistory.pushState({
+                                    action: 'load_talk',
+                                    lot: iLotId,
+                                    area: _oMessenger.oSettings.area_type,
+                                    jot: iJotId
+                                }, null);
+
+                            if (_oMessenger.aLoadingRequestsPool.length) {
+                                if (_oMessenger.aLoadingRequestsPool.length > 1)
+                                    _oMessenger.aLoadingRequestsPool = _oMessenger.aLoadingRequestsPool.slice(-1);
+                                else
+                                    _oMessenger.aLoadingRequestsPool.shift();
+
+                                fLoading();
+                            }
+
+                            _oMessenger.setScrollBarWidth();
+                        });
+                }
+            }
+
+            const { talkItem } = window.oMessengerSelectors.TALKS_LIST,
+                { infoColumn } = window.oMessengerSelectors.INFO;
+
+            _oMessenger.selectLotEmit($(`[data-lot="${iLotId}"]${talkItem}`));
+            _oMessenger.aLoadingRequestsPool.push(iLotId);
+
+            if ($(infoColumn).is(':visible') && !_oMessenger.isBlockVersion())
+                _oMessenger.oMenu.showInfoPanel();
+
+            if (!iLength)
+                fLoading();
+
+            return this;
+        },
+        onNextSearch: function () {
+            const iLotId = _oMessenger.oSettings.lot,
+                { jotMain, selectedJot } = window.oMessengerSelectors.JOT;
+
+            let iLeftJotId = 0;
+            if (typeof _oMessenger.aSearchJotsList[iLotId] !== 'undefined') {
+                const { list, current } = _oMessenger.aSearchJotsList[iLotId];
+                if (typeof current === 'undefined' || (current === list.length - 1))
+                    _oMessenger.aSearchJotsList[iLotId].current = 0;
+                else
+                    _oMessenger.aSearchJotsList[iLotId].current++;
+
+                iLeftJotId = list[current || 0];
+            }
+
+            if (iLeftJotId) {
+                const oPrevJot = $(`${jotMain}[data-id="${iLeftJotId}"]`);
+                if (oPrevJot.length)
+                    _oMessenger.updateScrollPosition('center', 'fast', oPrevJot.addClass(selectedJot.substr(1)));
+                else
+                    _oMessenger.jumpToJot(iLeftJotId, () => {
+                        $(`${jotMain}[data-id="${iLeftJotId}"]`).addClass(selectedJot.substr(1));
+                    });
+
+                _oMessenger.showSearchCounter(iLotId);
+            }
+
+            return this;
+        },
+        onScrollDown: function () {
+            const {
+                iUnreadJotsNumber,
+                iMaxHistory,
+                iSelectedJot,
+                oSettings: { lot },
+                iScrollDownPositionJotId
+            } = _oMessenger,
+                { talkListJotSelector } = window.oMessengerSelectors.JOT,
+                { unreadJotsCounter } = window.oMessengerSelectors.HISTORY;
+
+            if (iScrollDownPositionJotId) {
+                const oPrevJot = $(`${jotMain}[data-id="${iScrollDownPositionJotId}"]`);
+                if (oPrevJot.length)
+                    _oMessenger.updateScrollPosition('center', 'fast', oPrevJot);
+                else
+                    _oMessenger.jumpToJot(iScrollDownPositionJotId);
+
+                _oMessenger.iScrollDownPositionJotId = 0;
+                return;
+            }
+
+            if (iMaxHistory >= iUnreadJotsNumber && !iSelectedJot)
+                _oMessenger.updateScrollPosition('bottom', 'fast');
+            else {
+                const iLastJotId = $(talkListJotSelector).last().data('id');
+                _oMessenger.loadJotsForLot(lot, iLastJotId);
+
+                $(unreadJotsCounter)
+                    .text('')
+                    .hide();
+            }
+
+            return this;
+        },
+        searchByItems: function (sText) {
+            _oMessenger.searchByItems(sText);
+            return this;
+        },
+        onSaveParticipantsList: function (iLotId) {
+            _oMessenger.saveParticipantsList(iLotId);
+            return this;
+        },
+        onLeaveLot: function (iLotId) {
+            bx_confirm(_t('_bx_messenger_are_you_sure_leave', _oMessenger.oSettings.title), () => _oMessenger.leaveLot(iLotId));
+            return this;
+        },
+        onLotSettings: function () {
+            $.post('modules/?r=messenger/get_lot_settings', { lot: _oMessenger.oSettings.lot }, (oData) => {
+                processJsonData(oData);
+            }, 'json');
+
+            return this;
+        },
+        onSaveLotSettings: function (oEl) {
+            const aOptions = [];
+            $(oEl)
+                .closest('form')
+                .find('input[type="checkbox"]:checked')
+                .each(function () {
+                    aOptions.push($(this).prop('value'));
+                });
+
+            $.post('modules/?r=messenger/save_lot_settings', {
+                lot: _oMessenger.oSettings.lot,
+                options: aOptions
+            }, ({ code, msg }) => {
+                $(oEl)
+                    .closest('.bx-popup-applied:visible')
+                    .dolPopupHide();
+
+                if (+code && msg)
+                    bx_alert(msg);
+
+            }, 'json');
+
+        },
+        onViewDeletedJot: function (iJotId) {
+            _oMessenger.viewJot(iJotId);
+            return this;
+        },
+        onMuteLot: function (oEl, iLotId) {
+            _oMessenger.muteLot(iLotId, oEl);
+            return this;
+        },
+        onStarLot: function (oEl, iLotId) {
+            _oMessenger.starLot(iLotId, oEl);
+            return this;
+        },
+        onDeleteLot: function (iLotId) {
+            bx_confirm(_t('_bx_messenger_delete_lot', _oMessenger.oSettings.title), () => _oMessenger.deleteLot(iLotId));
+            return this;
+        },
+        onClearLot: function (iLotId) {
+            bx_confirm(_t('_bx_messenger_clear_lot', _oMessenger.oSettings.title), () => _oMessenger.clearLot(iLotId));
+            return this;
+        },
+        onDeleteJot: function (oObject, bCompletely) {
+            const { jotMain } = window.oMessengerSelectors.JOT,
+                { talkBlock, conversationBody } = window.oMessengerSelectors.HISTORY;
+
+            bx_confirm(_t('_bx_messenger_remove_jot_confirm'), () => {
+                oMessengerJotMenu.deleteJot(oObject, bCompletely, function (oInfo) {
+                    if (!_oMessenger.isBlockVersion())
+                        _oMessenger.upLotsPosition($.extend(oInfo, _oMessenger.oSettings));
+
+                    _oMessenger.broadcastMessage(oInfo);
+                }, () => {
+                    if ($(conversationBody).prop('scrollHeight') <= $(talkBlock).prop('clientHeight') && $(jotMain, talkBlock).length === 1)
+                        _oMessenger.updateJots({
+                            action: 'prev',
+                            position: 'bottom'
+                        });
+                    else
+                        _oMessenger.updateScrollPosition('bottom');
+                });
+            });
+        },
+        onEditJot: function (oObject) {
+            _oMessenger.editJot(oObject);
+        },
+        onSaveJot: function (oObject) {
+            _oMessenger.saveJot(oObject);
+        },
+        onSaveJotItem: function (oObject) {
+            _oMessenger.onSaveJotItem(oObject);
+        },
+        onCancelEdit: function (oObject) {
+            _oMessenger.cancelEdit(oObject);
+        },
+        onCopyJotLink: function (oObject) {
+            _oMessenger.copyJotLink(oObject);
+        },
+        onReplyJot: function (oObject) {
+            _oMessenger.replyJot(oObject);
+        },
+        onCleanReplayArea: function () {
+            _oMessenger.cleanReplayArea();
+        },
+        jumpToParentMessage: function (oElement, iJumpJotId) {
+            const { jotMain } = window.oMessengerSelectors.JOT;
+            if (iJumpJotId && oElement)
+                _oMessenger.iScrollDownPositionJotId = $(oElement).closest(jotMain).data('id');
+
+            _oMessenger.jumpToJot(iJumpJotId);
+        },
+        loadThreadsParent: function (iLotId, sType, iJotId) {
+            $('#bx-messenger-menu-block a').removeClass('active');
+            $('p[data-talks-type="threads"]').closest('a').addClass('active');
+            _oMessenger.loadTalksListByParam({ group: sType }, () => {
+                _oMessenger.loadTalk(iLotId, iJotId);
+            });
+        },
+        /**
+         * Methods below occur when messenger gets data from the server
+         */
+        onTyping: function (oData) {
+            _oMessenger.showTyping(oData);
+            return this;
+        },
+        onMedia: function () {
+            const { infoColumnContent } = window.oMessengerSelectors.INFO,
+                { attPrefixSelector } = window.oMessengerSelectors.ATTACHMENTS;
+
+            if (!_oMessenger.isBlockVersion())
+                _oMessenger.oMenu.showInfoPanel();
+            else
+                _oMessenger.oMenu.toggleBlockInfoPanel();
+
+            // remove previous content
+            $(infoColumnContent).html('');
+            return _oMessenger.loadTalkFiles($(infoColumnContent), $(`[class^="${attPrefixSelector}"]`, infoColumnContent).length, () =>
+                $(infoColumnContent).initLazyLoading(
+                    function () {
+                        _oMessenger.loadTalkFiles($(infoColumnContent), $(`[class^="${attPrefixSelector}"]`, infoColumnContent).length)
+                    }
+                )
+            );
+        },
+        onLotInfo: function () {
+            const { infoColumnContent } = window.oMessengerSelectors.INFO,
+                { attPrefixSelector } = window.oMessengerSelectors.ATTACHMENTS;
+
+            if (!_oMessenger.isBlockVersion())
+                _oMessenger.oMenu.showInfoPanel();
+            else
+                _oMessenger.oMenu.toggleBlockInfoPanel();
+
+            // remove previous content
+            $(infoColumnContent).html('');
+            return _oMessenger.loadTalkInfo($(infoColumnContent));
+        },
+        onToggleInfoPanel: function () {
+            if (!_oMessenger.isBlockVersion())
+                _oMessenger.oMenu.showInfoPanel();
+            else
+                _oMessenger.oMenu.toggleBlockInfoPanel();
+
+            return this;
+        },
+        onSelectItem: function (oObject, iLotId) {
+            _oMessenger.oMenu.toggleBlockGroupsPanel();
+        },
+        onMessage: function (oData) {
+            const bSilent = _oMessenger.oSettings.user_id === oData.user_id || (oData.type === 'vc' && oData.vc !== 'start');
+            try {
+                if (!_oMessenger.isBlockVersion())
+                    _oMessenger.upLotsPosition(oData, bSilent);
+
+            } catch (e) {
+                console.log('Lot list message update error', e);
+            }
+
+            try {
+                if (+oData.lot === +_oMessenger.oSettings.lot)
+                    _oMessenger.updateJots(oData, bSilent);
+
+                if (oData.type === 'vc' && +oData.lot)
+                    _oMessenger.getVideoCall(oData);
+
+            } catch (e) {
+                console.log('Talk history update error', e);
+            }
+
+            return this;
+        },
+        onStatusUpdate: function (oData) {
+            _oMessenger.updateStatuses(oData);
+            return this;
+        },
+        onServerResponse: function (oData) {
+            const { addon } = oData;
+            return this;
+        },
+
+        /**
+         * Occurs on image click, allows to make image bigger in popup.
+         *@param int iId image file id
+         */
+        zoomImage: function (iId) {
+            const { bigImag } = window.oMessengerSelectors.IMAGE_ZOOM;
+
+            $(window).dolPopupAjax({
+                url: 'modules/?r=messenger/get_big_image/' + iId + '/' + $(window).width() + '/' + $(window).height(),
+                id: { force: true, value: bigImag.substr(1) },
+                top: '0px',
+                left: '0px',
+                onBeforeShow: function () {
+                    $(`${bigImag}, ${bigImag} .bx-popup-element-close, ${bigImag} img, #bx-popup-fog`).click(function () {
+                        $(bigImag).dolPopupHide().remove();
+                    });
+                },
+                onShow: () => $(`${bigImag}`).waitForImages(() => $(bigImag).dolPopupCenter())
+            });
+        },
+
+        /**
+         * Select giphy item to send, allows to add message to gif image.
+         *@param string sId of the image
+         */
+        onSelectGiphy: function (oElement) {
+            const oUploader = _oMessenger.oFilesUploader,
+                { attachmentArea } = window.oMessengerSelectors.TEXT_AREA,
+                { giphyMain, giphyWrapper } = window.oMessengerSelectors.GIPHY;
+
+            if (oUploader && (oUploader.getFiles().length || oUploader.isLoadingStarted())) {
+                $(giphyMain).fadeOut();
+                return;
+            } else
+                $(giphyMain)
+                    .fadeOut(() => {
+                        const oObject = $(oElement)
+                            .clone()
+                            .wrap(`<div class="${giphyWrapper.substr(1)}"></div>`)
+                            .parent();
+
+                        _oMessenger.updateSendArea(false);
+                        $(attachmentArea)
+                            .html(
+                                oObject
+                                    .append(
+                                        $(`<i class="sys-icon times"></i>`)
+                                            .on('click', function () {
+                                                $(this)
+                                                    .closest(giphyWrapper)
+                                                    .fadeOut()
+                                                    .remove();
+
+                                                _oMessenger.updateSendArea(true);
+                                            })
+                                    )
+                            );
+                    });
+
+        },
+        initJitsi: function (oJitsi, bNew = false, bChatSync = false) {
+            _oMessenger.oJitsi = oJitsi;
+
+            if (oJitsi._lotId) {
+                const oInfo = { type: 'vc', vc: 'start' };
+
+                if (oJitsi._jotId && !bNew) {
+                    oInfo.jot_id = oJitsi._jotId;
+                    oInfo.vc = 'join';
+                } else {
+                    _oMessenger.playSound('call', true);
+                    _oMessenger.aJitisActiveUsers[oJitsi._lotId] = { owner: _oMessenger.oSettings.user_id, got: 0 };
+                }
+
+                _oMessenger.broadcastMessage(oInfo);
+                _oMessenger.updateJots({
+                    action: 'msg'
+                });
+
+                $(window)
+                    .on('beforeunload', () => _t('_bx_messenger_are_you_sure_close_jisti'))
+                    .on('unload', () => {
+                        $.get('modules/?r=messenger/stop_jvc/', { lot_id: oJitsi._lotId }, (oData) => {
+                            const oInfo = {
+                                jot_id: oJitsi._jotId,
+                                addon: 'vc',
+                                type: 'vc',
+                                vc: 'stop'
+                            };
+                            _oMessenger.broadcastMessage(oInfo);
+                            _oMessenger.updateJots(oInfo);
+                            _oMessenger.stopActiveSound();
+
+                        }, 'json');
+                        oJitsi.dispose();
+                    });
+
+                if (bChatSync)
+                    oJitsi.on('outgoingMessage', ({ message }) => {
+                        _oMessenger.sendMessage(message, { vc: oJitsi._lotId });
+                        _oMessenger.updateJots({
+                            action: 'msg'
+                        });
+                    });
+
+                oJitsi.on('videoConferenceLeft', () => {
+                    _oMessenger.closeJitsi(_oMessenger.sJitsiMain, oJitsi._lotId);
+                });
+            }
+        },
+
+        onJitsiClose: (oElement, iLotId) => {
+            _oMessenger.closeJitsi(oElement, iLotId);
+        },
+
+        /**
+         * Run Jitsi video chat
+         *@param string sId of the image
+         */
+
+        onStartVideoCall: function (oEl, iLotId, sRoom) {
+            _oMessenger.startVideoCall(oEl, iLotId, sRoom);
+        },
+
+        getCall: function (oEl, iLotId, sRoom, bAudioOnly = false) {
+            if (!iLotId)
+                return;
+
+            if (!_oMessenger.isActiveLot(iLotId))
+                _oMessenger.loadTalk(iLotId);
+
+            _oMessenger.startVideoCall(undefined, iLotId, sRoom, {
+                startAudioOnly: +bAudioOnly,
+                callback: () => {
+                    _oMessenger.onCloseCallPopup(oEl, iLotId, 'get_call', false);
+                }
+            });
+        },
+        /**
+         * Show only marked as important lot
+         *@param object oEl
+         */
+        showStarred: function (oEl) {
+            if (!_oMessenger.iStarredTalks) {
+                $(oEl)
+                    .addClass('active')
+                    .find('.star')
+                    .addClass('fill')
+            } else
+                $(oEl)
+                    .removeClass('active')
+                    .find('.star')
+                    .removeClass('fill');
+
+            _oMessenger.iStarredTalks = !_oMessenger.iStarredTalks;
+            _oMessenger.searchByItems();
+        },
+        removeFile: (oEl, id) => bx_confirm(_t('_bx_messenger_post_confirm_delete_file'), () => oMessengerJotMenu.removeFile(oEl, id)),
+        downloadFile: (iFileId) => oMessengerJotMenu.downloadFile(iFileId),
+        sendVideoRecord: function (oFile, oCallback) {
+            const { recorderComments, recordeWindow } = window.oMessengerSelectors.RECORDER;
+            let fileName = (new Date().getTime()),
+                formData = new FormData();
+
+            if (oFile.type !== undefined) {
+                let sExt = (~oFile.type.indexOf(';') ? oFile.type.substring(0, oFile.type.lastIndexOf(';')) : oFile.type).replace('video/', '.');
+                fileName += sExt;
+            }
+            ;
+
+            formData.append('name', fileName);
+            formData.append('file', oFile);
+
+            bx_loading($(_oMessenger.sFilesUploadAreaOnForm), true);
+            $.ajax({
+                url: 'modules/?r=messenger/upload_video_file',
+                data: formData,
+                type: 'POST',
+                dataType: 'json',
+                contentType: false,
+                processData: false,
+            })
+                .done(
+                    function (oData) {
+                        bx_loading($(_oMessenger.sFilesUploadAreaOnForm), false);
+                        if (!parseInt(oData.code)) {
+                            const sMessage = $(recorderComments).text();
+                            _oMessenger.sendMessage(sMessage, {
+                                files: [{
+                                    complete: 1,
+                                    realname: fileName,
+                                    name: fileName
+                                }]
+                            }, function (iJotId) {
+                                if (typeof oCallback == 'function')
+                                    oCallback();
+
+                                _oMessenger.attacheFiles(iJotId);
+                            });
+                        } else
+                            bx_alert(oData.message);
+                    });
+
+
+        },
+        updateCommentsAreaWidth: function (fWidth) {
+            _oMessenger.updateCommentsAreaWidth(fWidth);
+        },
+        onAddReaction: function (oObject, bNear) {
+            _oMessenger.onAddReaction(oObject, bNear);
+        },
+        onRemoveReaction: function (oObject) {
+            const _this = this,
+                { jotMain } = window.oMessengerSelectors.JOT,
+                oEmoji = $(oObject),
+                oReactionArea = oEmoji.closest(_oMessenger.sReactionsArea),
+                iJotId = +$(oEmoji).closest(jotMain).data('id'),
+                sEmojiId = $(oEmoji).data('emoji'),
+                fUpdateParams = () => {
+                    $(oEmoji).data('part', aParts.join(','));
+                    $(oEmoji).data('count', aParts.length);
+                    _oMessenger.broadcastMessage({
+                        jot_id: iJotId,
+                        addon: 'reaction'
+                    });
+                };
+
+            if (!iJotId)
+                return;
+
+            let iCount = +$(oEmoji).data('count'),
+                aParts = $(oEmoji)
+                    .data('part')
+                    .toString()
+                    .split(',');
+
+            if (~aParts.indexOf(_oMessenger.oSettings.user_id + '')) {
+                if (iCount === 1)
+                    $(oEmoji)
+                        .fadeOut()
+                        .remove();
+                else
+                    $('> span.count', oEmoji)
+                        .text(--iCount);
+
+                aParts = aParts.filter((iPart) => {
+                    return +iPart !== _oMessenger.oSettings.user_id
+                });
+
+                $.get('modules/?r=messenger/update_reaction', {
+                    jot: iJotId,
+                    emoji_id: sEmojiId,
+                    action: 'remove'
+                }, function (oData) {
+                    if (!parseInt(oData.code)) {
+                        fUpdateParams();
+                        if (!$(_oMessenger.sReactionItem, oReactionArea).length)
+                            $(_oMessenger.sReactionMenu, oReactionArea).fadeOut();
+                    }
+                }, 'json');
+            } else {
+                iCount++;
+                aParts.push(_oMessenger.oSettings.user_id);
+                if ($('> span.count', oEmoji).length)
+                    $('> span.count', oEmoji).text(iCount);
+                else
+                    $(oEmoji).append(`<span>${iCount}</span>`);
+
+                _oMessenger.playSound('reaction');
+                $.get('modules/?r=messenger/update_reaction', {
+                    jot: iJotId,
+                    emoji_id: sEmojiId,
+                    action: 'add'
+                }, function (oData) {
+                    if (!parseInt(oData.code))
+                        fUpdateParams();
+                }, 'json');
+            }
+        },
+        updateJots(oJots, sType) {
+            if (Array.isArray(oJots))
+                oJots.map(iJot => _oMessenger.updateJots({
+                    action: sType || 'edit',
+                    jot_id: iJot
+                }));
+        },
+        onHangUp: (oEl, iLotId) => {
+            _oMessenger.onCloseCallPopup(oEl, iLotId);
+        },
+        stopActiveSound: () => _oMessenger.stopActiveSound(),
+        showInfoMenu: (oMenu, sLotMenuId) => {
+            $(`#${sLotMenuId}`).dolPopup({
+                pointer: { el: $(oMenu).parent() },
+                moveToDocRoot: _oMessenger.isBlockVersion(),
+                onShow: function (oEl) {
+                    $(oEl).unbind('click').click(() => {
+                        $(oEl).hide()
+                    });
+                }
+            })
+        },
+        getVideoRecording: async (oButton) => {
+            const sText = _oMessenger.oEditor ? _oMessenger.oEditor.getText() : '',
+                { recorderComments, recordeWindow } = window.oMessengerSelectors.RECORDER;
+
+            if (navigator.mediaDevices === undefined) {
+                bx_alert(_t('_bx_messenger_video_recorder_is_not_available'));
+                return;
+            }
+
+            try {
+                bx_loading_btn(oButton, true);
+                await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
+                $(window).dolPopupAjax({
+                    url: 'modules/?r=messenger/get_record_video_form',
+                    id: { force: true, value: recordeWindow.substr(1) },
+                    onShow: function () {
+                        bx_loading_btn(oButton, false);
+                        if (typeof fCallback == 'function')
+                            fCallback();
+
+                        if (sText && sText.length)
+                            $(recorderComments).text(sText);
+
+                        setTimeout(() => _oMessenger.updateCommentsAreaWidth(), 100);
+                    },
+                    closeOnOuterClick: false,
+                    removeOnClose: true,
+                    onHide: function () {
+                        _oMessenger.updateScrollPosition('bottom');
+                    }
+                });
+            } catch ({ name }) {
+                switch (name) {
+                    case 'NotAllowedError':
+                        bx_alert(_t('_bx_messenger_video_recorder_is_blocked', sUrlRoot));
+                        break;
+                    default:
+                        bx_alert(_t('_bx_messenger_video_recorder_is_not_available'));
+                }
+
+                bx_loading_btn(oButton, false);
+            }
+        },
+        getUserByTerm: function (sTerm) {
+            const _this = _oMessenger,
+                { group_id, area_type, lot } = _this.oSettings,
+                { foundUsersArea, selectedUsersListInputs } = window.oMessengerSelectors.CREATE_TALK,
+                fExecute = () => {
+                    bx_loading($(foundUsersArea), true);
+                    $.post('modules/?r=messenger/get_users_list', {
+                        group_id, area_type, lot, term: sTerm, except: function () {
+                            let aUsers = [];
+                            $(selectedUsersListInputs)
+                                .each(function () {
+                                    aUsers.push($(this).val());
+                                });
+
+                            return aUsers.join(',');
+                        }
+                    }, ({ content }) => $(foundUsersArea).html(content), 'json');
+                };
+
+            clearTimeout(_this.iTimer);
+            _this.iTimer = setTimeout(fExecute, _this.iRunSearchInterval);
+        },
+        createList: function (action = 'new', fCallback) {
+            const { lot, area_type } = _oMessenger.oSettings;
+
+            _oMessenger.createList(action, fCallback);
+            _oMessenger.oHistory.pushState({ action: 'create_list', type: action, lot, area: area_type }, null);
+        },
+        onSelectUser: function (oUser) {
+            const { selectedUsersArea, existedUsersArea } = window.oMessengerSelectors.CREATE_TALK;
+
+            if (typeof oUser !== 'undefined') {
+                const iId = $(oUser).data('id');
+
+                if (!$(existedUsersArea, selectedUsersArea).find(`[data-id=${iId}]`).length) {
+                    const sUser = $('.avatar', oUser).html(),
+                        sUsername = $('.username', oUser).html(),
+                        oUserObject = $(_oMessenger.sAddUserTemplate).clone(),
+                        sTemplate = oUserObject
+                            .prop('outerHTML')
+                            .replace(/{id}/g, iId)
+                            .replace(/{avatar}/g, sUser)
+                            .replace(/{name}/g, sUsername);
+
+
+                    $(existedUsersArea, selectedUsersArea)
+                        .prepend($(sTemplate).append(`<input type="hidden" name="users[]" value="${iId}" />`));
+                }
+
+                $(oUser).remove();
+            }
+        },
+        loadTalksList: function (oMenu, mixedGroup) {
+            const { group } = mixedGroup,
+                {
+                    talksListItems,
+                    showImportantButton,
+                    searchCriteria,
+                    searchInput,
+                    inboxAreaTitle
+                } = window.oMessengerSelectors.TALKS_LIST;
+
+            _oMessenger.oMenu.toggleMenuPanel();
+            _oMessenger.oMenu.toggleAlwaysOnTop(false);
+            _oMessenger.sFilterType = group || '';
+
+            if ($(searchInput).is(':visible')) {
+                $(inboxAreaTitle).fadeIn();
+                $(searchInput).hide();
+                $(searchCriteria).val('');
+            }
+
+            if (_oMessenger.iStarredTalks) {
+                $(showImportantButton)
+                    .removeClass('active')
+                    .find('.star')
+                    .removeClass('fill');
+
+                _oMessenger.iStarredTalks = false;
+            }
+
+            _oMessenger.loadTalksListByParam(mixedGroup, () => {
+                const oLotId = $(talksListItems).first(),
+                    iLotId = oLotId && +oLotId.data('lot');
+
+                if (iLotId && !oMUtils.isMobile())
+                    _oMessenger.loadTalk(iLotId);
+
+                if (typeof _oMessenger.oHistory.pushState === 'function') {
+                    const oParentMenu = $(oMenu).closest("li.bx-menu-item");
+
+                    let sParentModule = '';
+                    if (oParentMenu.length)
+                        sParentModule = oParentMenu.attr('class').replace(/bx-menu-item-?|\s/ig, '');
+
+                    _oMessenger.oHistory.pushState({
+                        action: 'load_talk',
+                        lot: iLotId,
+                        area: _oMessenger.oSettings.area_type,
+                        menu: sParentModule
+                    }, null);
+                }
+            });
+
+        },
+        loadTalkWithArea: function (iLotId, iJotId) {
+            const sArea = 'inbox',
+                oParams = { group: sArea, lot: iLotId };
+
+            _oMessenger.loadTalksListByParam(oParams, () => {
+                _oMessenger.loadTalk(iLotId, iJotId); // set previous area type value to allow to load new talk, because area should be different
+                _oMessenger.oHistory.pushState({ action: 'load_talk', lot: iLotId, jot: iJotId }, null);
+            });
+        },
+        onReplyInThread: function (oObject) {
+            const { jotMain } = window.oMessengerSelectors.JOT,
+                oParams = { group: 'threads' };
+
+            if (_oMessenger.isBlockVersion()) {
+                window.location.href = _oMessenger.sJotUrl + +$(oObject).closest(jotMain).data('id');
+                return;
+            }
+
+            _oMessenger.loadTalksListByParam(oParams, () => {
+                _oMessenger.onReplyInThread(oObject);
+                _oMessenger.oHistory.pushState({
+                    action: 'create_thread',
+                    lot: _oMessenger.oSettings.lot,
+                    jot: +$(oObject).closest(jotMain).data('id')
+                }, null);
+            });
+
+        },
+        toggleFilterTalks: function () {
+            const { searchCriteria, searchInput, inboxAreaTitle } = window.oMessengerSelectors.TALKS_LIST;
+            $(searchInput).toggle(function () {
+                if ($(this).is(':visible')) {
+                    $(searchCriteria).focus();
+                    $(inboxAreaTitle).hide();
+                } else
+                    $(inboxAreaTitle).fadeIn();
+            });
+        },
+        removeProfileItem: function (oElement) {
+            const { selectedUsersArea } = window.oMessengerSelectors.CREATE_TALK,
+                iId = +$(oElement).data('id');
+
+            $(`input[value=${iId}]`, selectedUsersArea).remove();
+            $(oElement).remove();
+        },
+        closeEditForm: function () {
+            _oMessenger.disableCreateList();
+        },
+        clearSearch: () => {
+            const { searchCriteria, searchCloseIcon } = window.oMessengerSelectors.TALKS_LIST;
+            $(searchCriteria).val('');
+            $(searchCloseIcon).hide();
+            _oMessenger.searchByItems();
+            _oMessenger.aSearchJotsList = null;
+        },
+		setCreateNew: function (bValue)	{
+			if (_oMessenger != undefined)
+				_oMessenger.bCreateNew = !!bValue;
+		}
+    }
+})(jQuery);
+
+/** @} */

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/attachment.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/create-list.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/giphy.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/history-header.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/lot-briefs.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/menu-column.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/message-menu.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/message.css


+ 278 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/messenger.css

@@ -0,0 +1,278 @@
+.bx-page-wrapper,
+.bx-content-container,
+.bx-db-container,
+.bx-content-main,
+#bx-content-main,
+.bx-layout-wrapper,
+.bx-layout-row,
+.bx-layout-col,
+.bx-layout-cell,
+.bx-page-block-container,
+.bx-content-container.bx-def-padding-sec-leftright,
+.bx-db-content {
+	height: 100%;
+	max-height:100%;
+	margin: 0;
+	padding:0;
+}
+
+#bx-content-main {
+	position:relative;
+}
+
+#bx-content-with-cover-wrapper {
+	display: flex;
+	flex-direction: column;
+	height: 100%;
+}
+
+#bx-content-container {
+	overflow: hidden;
+	position: relative;
+	padding: 0;
+	height: 100%;
+	min-height: 100%;
+}
+
+.bx-main {
+  height:100vh;
+  display:flex;
+  flex-direction:column;
+  min-height: 100%;
+}
+
+.bx-media-phone body{
+	min-height: auto;
+}
+
+.bx-main  > div,
+.bx-main  > main{
+	flex: 0 0 auto;
+}
+#bx-content-with-submenu-wrapper,
+#bx-content-wrapper {
+   flex: 1 1 0;
+   overflow:hidden;
+   max-height:100%;
+   height: 100%;
+   display: flex;
+   flex-direction: column;
+}
+/* In the theme shells <main> starts before the cover and holds #bx-content-wrapper, so it takes the wrapper's place in the column. */
+#bx-content-main:has(> #bx-content-wrapper) {
+   flex: 1 1 0;
+   overflow:hidden;
+   max-height:100%;
+   height: 100%;
+   display: flex;
+   flex-direction: column;
+}
+#bx-content-main:has(> #bx-content-wrapper) > :not(#bx-content-wrapper) {
+   flex: 0 0 auto;
+}
+
+.cd-main-content .content-wrapper
+{
+	margin-left:0 !important;
+}
+
+#bx-content-wrapper .bx-def-page-width
+{
+	width:100%;
+	padding:0;
+	max-width:100%;
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+}
+
+
+#bx-footer-wrapper {
+	display:none;
+}
+
+.bx-messenger-table-wrapper  .bx-messenger-table-wrapper-rows:first-child .cell{
+	height:100% !important;
+	min-height:50px;
+}
+
+.bx-messenger-block-lots
+{
+	border:0;
+}
+
+.bx-messenger-items-list ul > li.hidden + li:not(.hidden) {
+	border-top:0;
+}
+
+.bx-messenger-hide-mobile svg
+{
+	vertical-align:middle;
+}
+
+.bx-messenger-hide-mobile a
+{
+	font-size:1rem;
+	font-weight:700;
+	display:inline-flex;
+}
+
+div.bx-messenger-border.search .menu{
+	display: flex;
+	align-items: center;
+}
+
+div.bx-messenger-border.search span i {
+	font-size: 1.3rem;
+}
+
+div.bx-messenger-border.search span.star {
+	margin-right: 0.5rem;
+	margin-left: 0.5rem;
+}
+
+div.bx-messenger-border.search div:first-child 
+{
+	width:100%;
+	vertical-align:middle;
+	flex:0 1 auto;
+}
+
+div.bx-messenger-border.search span:first-child input
+{
+	padding:0;
+	background-color:rgba(0,0,0,0);
+}
+
+div.bx-messenger-border.search div:first-child input::placeholder
+{
+	color:inherit;
+}
+
+div.bx-messenger-border.search > span:first-child{
+	flex: 1 auto;
+}
+
+div.bx-messenger-border.search .star a
+{
+	opacity:0.5;
+}
+
+div.bx-messenger-border.search .star a.active
+{
+	opacity:1;
+}
+
+.bx-messenger-blink-jot
+{
+	animation:blink-jot 4s ease-in-out;
+}
+
+@keyframes blink-jot
+{ 
+	from {background-color:rgba(74,144,226,0.5);}
+	to {background-color:transparent;}
+}
+
+.bx-messenger-talk-header-usernames{
+	background-color: inherit;
+	border: 0;
+	padding:0;
+	height:auto;
+	line-height: normal;
+}
+
+.bx-messenger-talk-header-usernames > span{
+	display: flex;
+	align-items: center;
+}
+.bx-messenger-talk-header-usernames > span > span:first-child{
+	display: flex;
+	align-items: center;
+	border-radius: 1rem;
+	overflow: hidden;
+	margin-right: 0.5rem;
+}
+
+#bx-messenger-talk-title {
+	box-shadow: none;
+}
+
+.bx-messenger-block .bx-stl-mia.hidden {
+	display: none;
+}
+
+.bx-messenger-history-arrow #unread-jots-counter {
+	padding:0;
+}
+
+#bx-messenger-add-users .ui.search.category.focus .ui.input {
+	line-height: inherit !important;
+}
+
+.bx-messenger-jots-snip-title div.status {
+	line-height: .825rem;
+}
+
+.bx-messenger-groups-menu {
+	scrollbar-width: none;
+}
+
+.bx-messenger-info-bubble {
+	position:absolute;
+	top: -0.2rem;
+	right: -0.01rem;
+	font-size: 10px;
+	line-height: 1rem;
+	width: 1rem;
+	min-width: 1rem;
+	text-align: center;
+	justify-content: center;
+}
+
+.bx-messenger-menu-block .bx-menu-ver li span {
+	display: flex;
+}
+
+/* decorous, lucid templates corrections to make page adoptive - begin */
+
+.bx-decorous.bx-page-messenger #bx-content-wrapper #bx-content-container,
+.bx-lucid.bx-page-messenger #bx-content-wrapper #bx-content-container{
+	position: absolute;
+	width: 100%;
+}
+
+.bx-decorous.bx-page-messenger .cd-main-content-wrp,
+.bx-lucid.bx-page-messenger .cd-main-content-wrp {
+	flex: 1 1 auto;
+	height:100%;
+}
+
+.bx-decorous.bx-page-messenger .content-wrapper-wrp .content-wrapper,
+.bx-lucid.bx-page-messenger .content-wrapper-wrp .content-wrapper {
+	min-height: 100%;
+}
+
+.bx-decorous.bx-page-messenger .cd-main-content,
+.bx-lucid.bx-page-messenger .cd-main-content,
+.bx-decorous.bx-page-messenger .content-wrapper-wrp,
+.bx-lucid.bx-page-messenger .content-wrapper-wrp,
+.bx-decorous.bx-page-messenger .content-wrapper-wrp .content-wrapper {
+	height: 100%;
+}
+
+/* decorous, lucid templates corrections to make page adoptive - end */
+
+.bx-messenger-broadcast-filter-area label {
+	padding-left: 0.3rem;
+	padding-bottom: 0.3rem;
+}
+
+#bx-messenger-message-box {
+	max-height: 180px;
+	overflow-y: auto;
+}
+
+.bx-messenger-create-convo-menu {
+
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/talks-list.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/text-area.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/css/video-conference.css


+ 11 - 0
modules/boonex/messenger/updates/14.0.0_15.0.0/source/template/images/icons/std-icon.svg

@@ -0,0 +1,11 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="48" height="48" fill="url(#paint0_linear_221_2540)"/>
+<path d="M27.2559 36.8357C33.5304 36.4187 38.5284 31.3498 38.9397 24.9864C39.0201 23.7411 39.0201 22.4514 38.9397 21.2061C38.5284 14.8426 33.5304 9.7737 27.2559 9.35661C25.1152 9.21432 22.8804 9.21462 20.7441 9.35661C14.4696 9.7737 9.47161 14.8426 9.06036 21.2061C8.97988 22.4514 8.97988 23.7411 9.06036 24.9864C9.21015 27.304 10.2351 29.4499 11.4419 31.2619C12.1425 32.5305 11.6801 34.1137 10.9503 35.4967C10.4241 36.4939 10.161 36.9925 10.3723 37.3526C10.5835 37.7128 11.0554 37.7243 11.9991 37.7473C13.8655 37.7927 15.124 37.2636 16.123 36.5269C16.6896 36.1092 16.9729 35.9002 17.1682 35.8762C17.3634 35.8522 17.7477 36.0105 18.516 36.327C19.2066 36.6114 20.0084 36.7868 20.7441 36.8357C22.8804 36.9778 25.1152 36.9781 27.2559 36.8357Z" stroke="white" stroke-width="2.25" stroke-linejoin="round"/>
+<path d="M23.9929 23.5H24.0063M29.9862 23.5H29.9997M17.9996 23.5H18.0131" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
+<defs>
+<linearGradient id="paint0_linear_221_2540" x1="24" y1="0" x2="24" y2="48" gradientUnits="userSpaceOnUse">
+<stop stop-color="#0D9488"/>
+<stop offset="1" stop-color="#0F766E"/>
+</linearGradient>
+</defs>
+</svg>

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels