bug fixins (#2475)
[openemr.git] / portal / messaging / secure_chat.php
blob86b4946f22de25faf3341d6869ef5730ab701516
1 <?php
2 /**
3 * secure_chat.php
5 * @package OpenEMR
6 * @link https://www.open-emr.org
7 * @author Jerry Padgett <sjpadgett@gmail.com>
8 * @author Brady Miller <brady.g.miller@gmail.com>
9 * @copyright Copyright (c) 2016-2017 Jerry Padgett <sjpadgett@gmail.com>
10 * @copyright Copyright (c) 2019 Brady Miller <brady.g.miller@gmail.com>
11 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
14 namespace SMA_Common;
16 session_start();
17 if (isset($_SESSION['pid']) && isset($_SESSION['patient_portal_onsite_two'])) {
18 $pid = $_SESSION['pid'];
19 $ignoreAuth = true;
20 require_once(dirname(__FILE__) . "/../../interface/globals.php");
21 define('IS_DASHBOARD', false);
22 define('IS_PORTAL', $_SESSION['pid']);
23 } else {
24 session_destroy();
25 $ignoreAuth = false;
26 require_once(dirname(__FILE__) . "/../../interface/globals.php");
27 if (! isset($_SESSION['authUserID'])) {
28 $landingpage = "index.php";
29 header('Location: '.$landingpage);
30 exit;
32 $admin = sqlQueryNoLog(
33 "SELECT CONCAT(users.fname,' ',users.lname) as user_name FROM users WHERE id = ?",
34 array($_SESSION['authUserID'])
36 define('ADMIN_USERNAME', $admin['user_name']);
37 define('IS_DASHBOARD', $_SESSION['authUser']);
38 define('IS_PORTAL', false);
39 $_SERVER[REMOTE_ADDR] = 'admin::' . $_SERVER[REMOTE_ADDR];
43 define('C_USER', IS_PORTAL ? IS_PORTAL : IS_DASHBOARD);
45 if (isset($_REQUEST['fullscreen'])) {
46 $_SESSION['whereto'] = 'messagespanel';
47 define('IS_FULLSCREEN', true);
48 } else {
49 define('IS_FULLSCREEN', false);
52 define('CHAT_HISTORY', '150');
53 define('CHAT_ONLINE_RANGE', '1');
54 define('ADMIN_USERNAME_PREFIX', 'adm_');
56 abstract class Model
58 public $db;
60 public function __construct()
62 //$this->db = new \mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
66 abstract class Controller
68 private $_request, $_response, $_query, $_post, $_server, $_cookies, $_session;
69 protected $_currentAction, $_defaultModel;
71 const ACTION_POSTFIX = 'Action';
72 const ACTION_DEFAULT = 'indexAction';
74 public function __construct()
76 $this->_request = &$_REQUEST;
77 $this->_query = &$_GET;
78 $this->_post = &$_POST;
79 $this->_server = &$_SERVER;
80 $this->_cookies = &$_COOKIE;
81 $this->_session = &$_SESSION;
82 $this->init();
85 public function init()
87 $this->dispatchActions();
88 $this->render();
91 public function dispatchActions()
93 $action = $this->getQuery('action');
94 if ($action && $action .= self::ACTION_POSTFIX) {
95 if (method_exists($this, $action)) {
96 $this->setResponse(
97 call_user_func(array($this, $action), array())
99 } else {
100 $this->setHeader("HTTP/1.0 404 Not Found");
102 } else {
103 $this->setResponse(
104 call_user_func(array($this, self::ACTION_DEFAULT), array())
108 return $this->_response;
111 public function render()
113 if ($this->_response) {
114 if (is_scalar($this->_response)) {
115 echo $this->_response;
116 } else {
117 throw new \Exception('Response content must be scalar');
120 exit;
124 public function indexAction()
126 return null;
129 public function setResponse($content)
131 $this->_response = $content;
134 public function setHeader($params)
136 if (! headers_sent()) {
137 if (is_scalar($params)) {
138 header($params);
139 } else {
140 foreach ($params as $key => $value) {
141 header(sprintf('%s: %s', $key, $value));
146 return $this;
149 public function setModel($namespace)
151 $this->_defaultModel = $namespace;
152 return $this;
155 public function setSession($key, $value)
157 $_SESSION[$key] = $value;
158 return $this;
161 public function setCookie($key, $value, $seconds = 3600)
163 $this->_cookies[$key] = $value;
164 if (! headers_sent()) {
165 setcookie($key, $value, time() + $seconds);
166 return $this;
170 public function getRequest($param = null, $default = null)
172 if ($param) {
173 return isset($this->_request[$param]) ?
174 $this->_request[$param] : $default;
177 return $this->_request;
180 public function getQuery($param = null, $default = null)
182 if ($param) {
183 return isset($this->_query[$param]) ?
184 $this->_query[$param] : $default;
187 return $this->_query;
190 public function getPost($param = null, $default = null)
192 if ($param) {
193 return isset($this->_post[$param]) ?
194 $this->_post[$param] : $default;
197 return $this->_post;
200 public function getServer($param = null, $default = null)
202 if ($param) {
203 return isset($this->_server[$param]) ?
204 $this->_server[$param] : $default;
207 return $this->_server;
210 public function getSession($param = null, $default = null)
212 if ($param) {
213 return isset($this->_session[$param]) ?
214 $this->_session[$param] : $default;
217 return $this->_session;
220 public function getCookie($param = null, $default = null)
222 if ($param) {
223 return isset($this->_cookies[$param]) ?
224 $this->_cookies[$param] : $default;
227 return $this->_cookies;
230 public function getUser()
232 return $this->_session['ptName'] ? $this->_session['ptName'] : $this->_session['authUser'];
234 public function getIsPortal()
236 return IS_PORTAL;
238 public function getIsFullScreen()
240 return IS_FULLSCREEN;
242 public function getModel()
244 if ($this->_defaultModel && class_exists($this->_defaultModel)) {
245 return new $this->_defaultModel;
249 public function sanitize($string, $quotes = ENT_QUOTES, $charset = 'utf-8')
251 return htmlentities($string, $quotes, $charset);
255 abstract class Helper
260 namespace SMA_Msg;
262 // @codingStandardsIgnoreStart
263 use SMA_Common;
264 // @codingStandardsIgnoreEnd
265 class Model extends SMA_Common\Model
267 public function getAuthUsers()
269 $resultpd = array();
270 $result = array();
271 if (!IS_PORTAL) {
272 $query = "SELECT patient_data.pid as recip_id, Concat_Ws(' ', patient_data.fname, patient_data.lname) as username FROM patient_data " .
273 "LEFT JOIN patient_access_onsite pao ON pao.pid = patient_data.pid " .
274 "WHERE patient_data.pid = pao.pid AND pao.portal_pwd_status = 1";
275 $response = sqlStatementNoLog($query);
276 while ($row = sqlFetchArray($response)) {
277 $resultpd[] = $row;
280 if (IS_PORTAL) {
281 $query = "SELECT users.username as recip_id, users.authorized as dash, CONCAT(users.fname,' ',users.lname) as username " .
282 "FROM users WHERE active = 1 AND username > ''";
283 $response = sqlStatementNoLog($query);
285 while ($row = sqlFetchArray($response)) {
286 $result[] = $row;
289 $all = array_merge($result, $resultpd);
291 return json_encode($all);
293 public function getMessages($limit = CHAT_HISTORY, $reverse = true)
295 $response = sqlStatementNoLog("(SELECT * FROM onsite_messages
296 ORDER BY `date` DESC LIMIT " . escape_limit($limit) . ") ORDER BY `date` ASC");
298 $result = array();
299 while ($row = sqlFetchArray($response)) {
300 if (IS_PORTAL || IS_DASHBOARD) {
301 $u = json_decode($row['recip_id'], true);
302 if (!is_array($u)) {
303 continue;
306 if ((in_array(C_USER, $u)) || $row['sender_id'] == C_USER) {
307 $result[] = $row; // only current patient messages
309 } else {
310 $result[] = $row; // admin gets all
314 return $result;
317 public function addMessage($username, $message, $ip, $senderid = 0, $recipid = '')
319 return sqlQueryNoLog("INSERT INTO onsite_messages VALUES (NULL, ?, ?, ?, NOW(), ?, ?)", array($username,$message,$ip,$senderid,$recipid));
322 public function removeMessages()
324 return sqlQueryNoLog("TRUNCATE TABLE onsite_messages");
327 public function removeOldMessages($limit = CHAT_HISTORY)
329 /* @todo Patched out to replace with soft delete. Besides this query won't work with current ado(or any) */
330 /* return sqlStatementNoLog("DELETE FROM onsite_messages
331 WHERE id NOT IN (SELECT id FROM onsite_messages
332 ORDER BY date DESC LIMIT {$limit})"); */
335 public function getOnline($count = true, $timeRange = CHAT_ONLINE_RANGE)
337 if ($count) {
338 $response = sqlStatementNoLog("SELECT count(*) as total FROM onsite_online");
339 return sqlFetchArray($response);
342 $response = sqlStatementNoLog("SELECT * FROM onsite_online");
343 $result = array();
344 while ($row = sqlFetchArray($response)) {
345 $result[] = $row;
348 return $result;
351 public function updateOnline($hash, $ip, $username = '', $userid = 0)
353 return sqlStatementNoLog("REPLACE INTO onsite_online
354 VALUES ( ?, ?, NOW(), ?, ? )", array($hash, $ip, $username, $userid)) or die(mysql_error());
357 public function clearOffline($timeRange = CHAT_ONLINE_RANGE)
359 return sqlStatementNoLog("DELETE FROM onsite_online
360 WHERE last_update <= (NOW() - INTERVAL " . escape_limit($timeRange) . " MINUTE)");
363 public function __destruct()
368 class Controller extends SMA_Common\Controller
370 protected $_model;
372 public function __construct()
374 $this->setModel('SMA_Msg\Model');
375 parent::__construct();
378 public function indexAction()
381 public function authusersAction()
383 return $this->getModel()->getAuthUsers(true);
385 public function listAction()
387 $this->setHeader(array('Content-Type' => 'application/json'));
388 $messages = $this->getModel()->getMessages();
389 foreach ($messages as &$message) {
390 $message['me'] = C_USER === $message['sender_id']; // $this->getServer('REMOTE_ADDR') === $message['ip'];
393 return json_encode($messages);
396 public function saveAction()
398 $username = $this->getPost('username');
399 $message = $this->getPost('message');
400 $ip = $this->getServer('REMOTE_ADDR');
401 $this->setCookie('username', $username, 9999 * 9999);
402 $recipid = $this->getPost('recip_id');
404 if (IS_PORTAL) {
405 $senderid = IS_PORTAL;
406 } else {
407 $senderid = IS_DASHBOARD;
410 $result = array('success' => false);
411 if ($username && $message) {
412 $cleanUsername = preg_replace('/^'.ADMIN_USERNAME_PREFIX.'/', '', $username);
413 $result = array(
414 'success' => $this->getModel()->addMessage($cleanUsername, $message, $ip, $senderid, $recipid)
418 if ($this->_isAdmin($username)) {
419 $this->_parseAdminCommand($message);
422 $this->setHeader(array('Content-Type' => 'application/json'));
423 return json_encode($result);
426 private function _isAdmin($username)
428 return IS_DASHBOARD?true:false;
429 //return preg_match('/^'.ADMIN_USERNAME_PREFIX.'/', $username);
432 private function _parseAdminCommand($message)
434 if (strpos($message, '/clear') !== false) {
435 $this->getModel()->removeMessages();
436 return true;
439 if (strpos($message, '/online') !== false) {
440 $online = $this->getModel()->getOnline(false);
441 $ipArr = array();
442 foreach ($online as $item) {
443 $ipArr[] = $item->ip;
446 $message = 'Online: ' . implode(", ", $ipArr);
447 $this->getModel()->addMessage('Admin Command', $message, '0.0.0.0');
448 return true;
452 private function _getMyUniqueHash()
454 $unique = $this->getServer('REMOTE_ADDR');
455 $unique .= $this->getServer('HTTP_USER_AGENT');
456 $unique .= $this->getServer('HTTP_ACCEPT_LANGUAGE');
457 $unique .= C_USER;
458 return md5($unique);
461 public function pingAction()
463 $ip = $this->getServer('REMOTE_ADDR');
464 $hash = $this->_getMyUniqueHash();
465 $user = $this->getRequest('username', 'No Username');
466 if ($user == 'currentol') {
467 $onlines = $this->getModel()->getOnline(false);
468 $this->setHeader(array('Content-Type' => 'application/json'));
469 return json_encode($onlines);
472 if (IS_PORTAL) {
473 $userid = IS_PORTAL;
474 } else {
475 $userid = IS_DASHBOARD;
478 $this->getModel()->updateOnline($hash, $ip, $user, $userid);
479 $this->getModel()->clearOffline();
480 // $this->getModel()->removeOldMessages(); // @todo For soft delete when I decide. DO NOT REMOVE
482 $onlines = $this->getModel()->getOnline();
484 $this->setHeader(array('Content-Type' => 'application/json'));
485 return json_encode($onlines);
489 $msgApp = new Controller();
491 <!doctype html>
492 <html ng-app="MsgApp">
493 <head>
494 <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
495 <meta charset="utf-8">
497 <title><?php echo xlt('Secure Patient Chat'); ?></title>
498 <meta name="author" content="Jerry Padgett sjpadgett{{at}} gmail {{dot}} com">
500 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery/dist/jquery.min.js"></script>
502 <link href="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
503 <?php if ($_SESSION['language_direction'] == 'rtl') { ?>
504 <link href="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-rtl/dist/css/bootstrap-rtl.min.css" rel="stylesheet" type="text/css" />
505 <?php } ?>
507 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap/dist/js/bootstrap.min.js"></script>
509 <link rel="stylesheet" href="<?php echo $GLOBALS['assets_static_relative']; ?>/summernote/dist/summernote.css" />
510 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/summernote/dist/summernote.js"></script>
512 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/angular/angular.min.js"></script>
513 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/angular-summernote/dist/angular-summernote.js"></script>
514 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/angular-sanitize/angular-sanitize.min.js"></script>
515 <script src='<?php echo $GLOBALS['assets_static_relative']; ?>/checklist-model/checklist-model.js'></script>
517 </head>
518 <script type="text/javascript">
519 (function() {
520 var MsgApp = angular.module('MsgApp',['ngSanitize','summernote',"checklist-model"]);
521 MsgApp.config(function( $compileProvider ) {
522 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|file|ftp|blob):|data:image\//);
525 MsgApp.directive('ngEnter', function () {
526 return function (scope, element, attrs) {
527 element.bind("keydown keypress", function (event) {
528 if (event.which === 13) {
529 scope.$apply(function (){
530 scope.$eval(attrs.ngEnter);
532 event.preventDefault();
537 MsgApp.directive('tooltip', function(e){
538 return {
539 restrict: 'A',
540 link: function(scope, element, attrs){
541 $(element).hover(function(){
542 $(element).tooltip('show');
543 }, function(){
544 $(element).tooltip('hide');
549 MsgApp.filter('unique', function() {
550 return function(collection, keyname) {
551 var output = [],
552 keys = [];
553 angular.forEach(collection, function(item) {
554 var key = item[keyname];
555 if(keys.indexOf(key) === -1) {
556 keys.push(key);
557 output.push(item);
560 return output;
564 MsgApp.controller('MsgAppCtrl', ['$scope', '$http', '$filter', function($scope, $http, $filter) {
565 $scope.urlListMessages = '?action=list'; // actions for restful
566 $scope.urlSaveMessage = '?action=save';
567 $scope.urlListOnlines = '?action=ping';
568 $scope.urlGetAuthUsers = '?action=authusers';
570 $scope.pidMessages = null;
571 $scope.pidPingServer = null;
573 $scope.beep = new Audio('beep.ogg'); // you've got mail!!!! really just a beep
574 $scope.messages = [];
575 $scope.online = null;
576 $scope.lastMessageId = null;
577 $scope.historyFromId = null;
578 $scope.onlines = []; // all online users id and ip's
579 $scope.user = <?php echo $_SESSION['ptName'] ? js_escape($_SESSION['ptName']) : js_escape(ADMIN_USERNAME); ?>;// current user - dashboard user is from session authUserID
580 $scope.userid = <?php echo IS_PORTAL ? js_escape($_SESSION['pid']) : js_escape($_SESSION['authUser']); ?>;
581 $scope.isPortal = "<?php echo IS_PORTAL;?>";
582 $scope.isFullScreen = "<?php echo IS_FULLSCREEN; ?>";
583 $scope.pusers = []; // selected recipients for chat
584 $scope.chatusers = []; // authorize chat recipients for dashboard user
585 $scope.noRecipError = <?php echo xlj("Please Select a Recipient for Message.") ?>;
586 $scope.me = {
587 username: $scope.user,
588 message: null,
589 sender_id: $scope.userid,
590 recip_id: 0
592 $scope.options = {
593 height: 200,
594 focus: true,
595 placeholder: 'Start typing your message...',
596 //direction: 'rtl',
597 toolbar: [
598 ['style', ['bold', 'italic', 'underline', 'clear']],
599 ['fontsize', ['fontsize']],
600 ['color', ['color']],
601 ['para', ['ul', 'ol', 'paragraph']],
602 ['insert', ['link','picture', 'video', 'hr']],
603 ['view', ['fullscreen', 'codeview']]
606 $scope.checkAll = function() {
607 $scope.pusers = [];
608 $scope.pusers = $scope.chatusers.map(function(item) { return item.recip_id; });
609 $scope.getAuthUsers();
611 $scope.uncheckAll = function() {
612 $scope.pusers = [];
613 $scope.getAuthUsers();
615 $scope.makeCurrent = function(sel) {
616 if( !sel.me ){
617 $scope.pusers.splice(0, $scope.pusers.length);
618 $scope.pusers.push(sel.sender_id);
621 $scope.pageTitleNotificator = {
622 vars: {
623 originalTitle: window.document.title,
624 interval: null,
625 status: 0
627 on: function(title, intervalSpeed) {
628 var self = this;
629 if (! self.vars.status) {
630 self.vars.interval = window.setInterval(function() {
631 window.document.title = (self.vars.originalTitle == window.document.title) ?
632 title : self.vars.originalTitle;
633 }, intervalSpeed || 500);
634 self.vars.status = 1;
637 off: function() {
638 window.clearInterval(this.vars.interval);
639 window.document.title = this.vars.originalTitle;
640 this.vars.status = 0;
644 $scope.editmsg = function() {
645 $('.summernote').summernote();
648 $scope.saveedit = function() {
649 var makrup = $('.summernote').summernote('code');
650 $scope.me.message = makrup;
651 $scope.saveMessage();
652 $('.summernote').summernote('code', ''); //add this options to reset editor or not-default is persistent content
653 //$('.summernote').summernote('destroy');
656 $scope.saveMessage = function(form, callback) {
657 $scope.me.recip_id = JSON.stringify(angular.copy($scope.pusers));
658 var data = $.param($scope.me);
659 if (! ($scope.me.username && $scope.me.username.trim())) {
660 return $scope.openModal();
662 if (! ($scope.me.message && $scope.me.message.trim() &&
663 $scope.me.username && $scope.me.username.trim())) {
664 return;
666 if($scope.me.recip_id == "[]") {
667 alert($scope.noRecipError);
668 return;
670 $scope.me.message = '';
671 return $http({
672 method: 'POST',
673 url: $scope.urlSaveMessage,
674 data: data,
675 headers: {'Content-Type': 'application/x-www-form-urlencoded'}
676 }).success(function(data) {
677 $scope.listMessages(true);
681 $scope.replaceShortcodes = function(message) {
682 var msg = '';
683 msg = message.toString().replace(/(\[img])(.*)(\[\/img])/, "<img class='img-responsive' src='$2' />");
684 msg = msg.toString().replace(/(\[url])(.*)(\[\/url])/, "<a href='$2'>$2</a>");
685 msg = message.toString().replace("<img ", "<img class='img-responsive' ");
686 return msg;
689 $scope.notifyLastMessage = function() {
690 if (typeof window.Notification === 'undefined') {
691 return;
693 window.Notification.requestPermission(function (permission) {
694 var lastMessage = $scope.getLastMessage();
695 if (permission == 'granted' && lastMessage && lastMessage.username) {
696 var notify = new window.Notification('Message notification from ' + lastMessage.username + ' : ', {
697 body: 'New message' //lastMessage.message
699 notify.onclick = function() {
700 window.focus();
702 notify.onclose = function() {
703 $scope.pageTitleNotificator.off();
705 var timmer = setInterval(function() {
706 notify && notify.close();
707 typeof timmer !== 'undefined' && window.clearInterval(timmer);
708 }, 60000);
713 $scope.getLastMessage = function() {
714 return $scope.messages[$scope.messages.length - 1];
717 $scope.listMessages = function(wasListingForMySubmission) {
718 return $http.post($scope.urlListMessages, {}).success(function(data) {
719 $scope.messages = [];
720 angular.forEach(data, function(message) {
721 message.message = $scope.replaceShortcodes(message.message);
722 $scope.messages.push(message);
725 var lastMessage = $scope.getLastMessage();
726 var lastMessageId = lastMessage && lastMessage.id;
728 if ($scope.lastMessageId !== lastMessageId) {
729 $scope.onNewMessage(wasListingForMySubmission);
731 $scope.lastMessageId = lastMessageId;
732 if($scope.pusers === ''){ // refresh current in chat list.
733 angular.forEach($filter('unique')($scope.messages,'sender_id'), function(m,k){
734 var flg = false;
735 angular.forEach($scope.pusers, function(id) {
736 if(id === m.sender_id){ flg = true; }
738 if(!flg) $scope.pusers.push(m.sender_id);
741 $scope.getOnlines();
745 $scope.onNewMessage = function(wasListingForMySubmission) {
746 if ($scope.lastMessageId && !wasListingForMySubmission) {
747 $scope.playAudio();
748 $scope.pageTitleNotificator.on('New message');
749 $scope.notifyLastMessage();
751 $scope.scrollDown();
752 window.addEventListener('focus', function() {
753 $scope.pageTitleNotificator.off();
757 $scope.getAuthUsers = function() {
758 $scope.chatusers = [];
759 return $http.post($scope.urlGetAuthUsers, {}).success(function(data) {
760 $scope.chatusers = data;
764 $scope.pingServer = function(msgItem) {
765 return $http.post($scope.urlListOnlines+'&username='+$scope.user, {}).success(function(data) {
766 $scope.online = data;
771 $scope.getOnlines = function() {
772 return $http.post($scope.urlListOnlines+'&username=currentol', {}).success(function(data) {
773 $scope.onlines = data;
777 $scope.init = function() {
778 $scope.listMessages();
779 $scope.pidMessages = window.setInterval($scope.listMessages, 6000);
780 $scope.pidPingServer = window.setInterval($scope.pingServer, 10000);
781 $scope.getAuthUsers();
782 $("#popeditor").on("show.bs.modal", function() {
783 var height = $(window).height() - 200;
784 $(this).find(".modal-body").css("max-height", height);
788 $scope.scrollDown = function() {
789 var pidScroll;
790 pidScroll = window.setInterval(function() {
791 $('.direct-chat-messages').scrollTop(window.Number.MAX_SAFE_INTEGER * 0.001);
792 window.clearInterval(pidScroll);
793 }, 100);
796 $scope.clearHistory = function() {
797 var lastMessage = $scope.getLastMessage();
798 var lastMessageId = lastMessage && lastMessage.id;
799 lastMessageId = (lastMessageId-1 >= 2) ? lastMessageId -1 : lastMessageId;
800 lastMessageId && ($scope.historyFromId = lastMessageId);
803 $scope.openModal = function(e) {
804 var mi = $('#popeditor').modal({backdrop: "static"});
805 //$scope.editmsg();
808 $scope.playAudio = function() {
809 $scope.beep && $scope.beep.play();
812 $scope.renderMessageBody = function(html)
814 return html;
816 $scope.init();
817 }]);
818 })();
819 </script>
820 <style>
821 .direct-chat-text {
822 border-radius:5px;
823 position:relative;
824 padding:5px 10px;
825 background:#FBFBFB;
826 border:1px solid #6a6a6a;
827 margin:5px 0 0 50px;
828 color:#444;
830 .direct-chat-msg,.direct-chat-text {
831 display:block;
832 word-wrap: break-word;
834 .direct-chat-img {
835 border-radius:50%;
836 float:left;
837 width:40px;
838 height:40px;
840 .direct-chat-info {
841 display:block;
842 margin-bottom:2px;
843 font-size:12px;
845 .direct-chat-msg {
846 margin-bottom:5px;
848 .direct-chat-messages,.direct-chat-contacts {
849 -webkit-transition:-webkit-transform .5s ease-in-out;
850 -moz-transition:-moz-transform .5s ease-in-out;
851 -o-transition:-o-transform .5s ease-in-out;
852 transition:transform .5s ease-in-out;
854 .direct-chat-messages {
855 -webkit-transform:translate(0,0);
856 -ms-transform:translate(0,0);
857 -o-transform:translate(0,0);
858 transform:translate(0,0);
859 padding: 5px;
860 height: calc(100vh - 175px);
861 /* height: 400px; */
862 /*height:100%; */
863 overflow:auto;
864 word-wrap: break-word;
866 .direct-chat-text:before {
867 border-width:6px;
868 margin-top:-6px;
870 .direct-chat-text:after {
871 border-width:5px;
872 margin-top:-5px;
874 .direct-chat-text:after,.direct-chat-text:before {
875 position:absolute;
876 right:100%;
877 top:15px;
878 border:solid rgba(0,0,0,0);
879 border-right-color:#D2D6DE;
880 content:' ';
881 height:0;
882 width:0;
883 pointer-events:none;
885 .direct-chat-warning .right>.direct-chat-text {
886 background: rgba(251, 255, 178, 0.34);
887 border-color: #f30d1b;
888 color:#000;
890 .right .direct-chat-text {
891 margin-right:50px;
892 margin-left:0;
894 .direct-chat-warning .right>.direct-chat-text:after,
895 .direct-chat-warning .right>.direct-chat-text:before {
896 border-left-color:#F39C12;
898 .right .direct-chat-text:after,.right .direct-chat-text:before {
899 right:auto;
900 left:100%;
901 border-right-color:rgba(0,0,0,0);
902 border-left-color:#D2D6DE;
904 .right .direct-chat-img {
905 float:right;
907 .box-footer {
908 border-top-left-radius:0;
909 border-top-right-radius:0;
910 border-bottom-right-radius:3px;
911 border-bottom-left-radius:3px;
912 border-top:1px solid #F4F4F4;
913 padding:10px 0;
914 background-color:#FFF;
916 .direct-chat-name {
917 font-weight:600;
919 .box-footer form {
920 margin-bottom:10px;
922 input,button,.alert,.modal-content {
923 border-radius: 0!important;
925 .ml10 {
926 margin-left:10px;
928 .ml5 {
929 margin-left:5px;
931 .sidebar{
932 background-color: ghostwhite;
933 height:100%;
934 margin-top:5px;
935 margin-right:0;
936 padding-right:5px;
937 /*max-height: 730px;*/
938 height: calc(100vh - 100px);
939 overflow: auto;
941 .rtsidebar{
942 background-color: ghostwhite;
943 height:100%;
944 margin-top:5px;
945 margin-right:0;
946 height: calc(100vh - 100px);
947 overflow: auto;
949 .fixed-panel{
950 height: 100%;
951 padding: 5px 5px 0 5px;
953 h5 {
954 font-size:16px !important;
956 label {display: block;}
957 legend{
958 font-size:14px;
959 margin-bottom:2px;
960 background:#fff;
962 .modal.modal-wide .modal-dialog {
963 width: 75%;
965 .modal-wide .modal-body {
966 overflow-y: auto;
968 </style>
970 <body ng-controller="MsgAppCtrl">
971 <div class="container">
972 <!-- <h2 class="hidden-xs">Secure Chat</h2> -->
973 <div class="row">
974 <div class="col-md-2 sidebar">
975 <h5><span class="label label-default"><?php echo xlt('Current Recipients'); ?></span></h5>
976 <label ng-repeat="user in chatusers | unique : 'username'" ng-if="pusers.indexOf(user.recip_id) !== -1 && user.recip_id != me.sender_id">
977 <input type="checkbox" data-checklist-model="pusers" data-checklist-value="user.recip_id"> {{user.username}}
978 </label>
979 <h5><span class="label label-default"><?php echo xlt('Available Recipients'); ?></span></h5>
980 <span>
981 <button id="chkall" class="btn btn-xs btn-success" ng-show="!isPortal" ng-click="checkAll()" type="button"><?php echo xlt('All'); ?></button>
982 <button id="chknone" class="btn btn-xs btn-success" ng-show="!isPortal" ng-click="uncheckAll()" type="button"><?php echo xlt('None'); ?></button>
983 </span>
984 <label ng-repeat="user in chatusers | unique : 'username'" ng-show="!isPortal || (isPortal && user.dash)">
985 <input type="checkbox" data-checklist-model="pusers" data-checklist-value="user.recip_id"> {{user.username}}
986 </label>
987 </div>
988 <div class="col-md-8 fixed-panel">
989 <div class="panel direct-chat direct-chat-warning">
990 <div class="panel-heading">
991 <div class="clearfix">
992 <a class="btn btn-sm btn-primary ml10" href=""
993 data-toggle="modal" data-target="#clear-history"><?php echo xlt('Clear history'); ?></a>
994 <a class="btn btn-sm btn-success pull-left ml10" href="./../patient/provider" ng-show="!isPortal"><?php echo xlt('Home'); ?></a>
995 <a class="btn btn-sm btn-success pull-left ml10" href="./../home.php" ng-show="isFullScreen"><?php echo xlt('Home'); ?></a>
996 </div>
997 </div>
998 <div class="panel-body">
999 <div class="direct-chat-messages">
1000 <div class="direct-chat-msg" ng-repeat="message in messages" ng-if="historyFromId < message.id" ng-class="{'right':!message.me}">
1001 <div class="direct-chat-info clearfix">
1002 <span class="direct-chat-name" ng-class="{'pull-left':message.me,'pull-right':!message.me}">{{message.username }}</span>
1003 <span class="direct-chat-timestamp " ng-class="{'pull-left':!message.me,'pull-right':message.me}">{{message.date }}</span>
1004 </div>
1005 <i class="direct-chat-img glyphicon glyphicon-hand-left"
1006 style="cursor: pointer;font-size:24px" ng-show="!message.me"
1007 ng-click="makeCurrent(message)"
1008 title="<?php echo xla('Click to activate and send to this recipient.'); ?>"></i>
1009 <i class="direct-chat-img glyphicon glyphicon-hand-right"
1010 style="cursor: pointer;font-size:24px" ng-show="message.me"
1011 ng-click="makeCurrent(message)"
1012 title="<?php echo xla('Click to activate and send to this recipient.'); ?>"></i>
1014 <div class="direct-chat-text right">
1015 <div style="padding-left: 0px; padding-right: 0px;"
1016 title="<?php echo xla('Click to activate and send to this recipient.'); ?>"
1017 ng-click="makeCurrent(message)"
1018 ng-bind-html=renderMessageBody(message.message)></div>
1019 </div>
1020 </div>
1021 </div>
1022 <div class="panel-footer box-footer-hide">
1023 <form id='msgfrm' ng-submit="saveMessage()">
1024 <div class="input-group">
1025 <input type="text" placeholder="Type message..." id="msgedit" autofocus="autofocus"
1026 class="form-control" ng-model="me.message" ng-enter="saveMessage()">
1027 <span class="input-group-btn">
1028 <button type="submit" class="btn btn-danger btn-flat"><?php echo xlt('Send'); ?></button>
1029 <button type="button" class="btn btn-success btn-flat" ng-click="openModal(event)"><?php echo xlt('Edit'); ?></button>
1030 </span>
1031 </div>
1032 </form>
1033 </div>
1034 </div>
1035 </div>
1036 </div>
1037 <div class="col-md-2 rtsidebar">
1038 <h5><span class="label label-default"><?php echo xlt('Whose Online'); ?> : {{ online.total || '0' }}</span>
1039 </h5>
1040 <label ng-repeat="ol in onlines | unique : 'username'">
1041 <input type="checkbox" data-checklist-model="onlines" data-checklist-value="ol"> {{ol.username}}
1042 </label>
1043 </div>
1044 </div>
1046 <div class="modal modal-wide fade" id="popeditor">
1047 <div class="modal-dialog modal-lg">
1048 <div class="modal-content">
1049 <form>
1050 <div class="modal-header">
1051 <button type="button" class="close" data-dismiss="modal">
1052 <span aria-hidden="true">&times;</span>
1053 <span class="sr-only"><?php echo xlt('Close'); ?></span>
1054 </button>
1055 <h4 class="modal-title"><?php echo xlt('Style your messsage and/or add Image/Video'); ?></h4>
1056 </div>
1057 <div class="modal-body">
1058 <summernote config="options"></summernote>
1059 </div>
1060 <div class="modal-footer">
1061 <button type="button" class="btn btn-sm" data-dismiss="modal"><?php echo xlt('Dismiss'); ?></button>
1062 <button type="button" class="btn btn-success" data-dismiss="modal" ng-click="saveedit()"><?php echo xlt('Send It'); ?></button>
1063 </div>
1064 </form>
1065 </div>
1066 </div>
1067 </div>
1068 <div class="modal" id="clear-history">
1069 <div class="modal-dialog">
1070 <div class="modal-content">
1071 <form>
1072 <div class="modal-header">
1073 <button type="button" class="close" data-dismiss="modal">
1074 <span aria-hidden="true">&times;</span>
1075 <span class="sr-only"><?php echo xlt('Close'); ?></span>
1076 </button>
1077 <h4 class="modal-title"><?php echo xlt('Chat history'); ?></h4>
1078 </div>
1079 <div class="modal-body">
1080 <label class="radio"><?php echo xlt('Are you sure to clear chat history?'); ?></label>
1081 </div>
1082 <div class="modal-footer">
1083 <button type="button" class="btn btn-sm btn-default" data-dismiss="modal"><?php echo xlt('Cancel'); ?></button>
1084 <button type="button" class="btn btn-sm btn-primary" data-dismiss="modal" ng-click="clearHistory()"><?php echo xlt('Accept'); ?></button>
1085 </div>
1086 </form>
1087 </div>
1088 </div>
1089 </div>
1091 </body>
1092 </html>