Merge pull request #2015 from bradymiller/rebase-danehrlich1-more-sql-changes_1
[openemr.git] / portal / messaging / secure_chat.php
blob6b440c646313acb74eb2e560e5e09df04c349b04
1 <?php
2 /**
4 * Copyright (C) 2016-2017 Jerry Padgett <sjpadgett@gmail.com>
6 * LICENSE: This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * @package OpenEMR
20 * @author Jerry Padgett <sjpadgett@gmail.com>
21 * @link http://www.open-emr.org
24 namespace SMA_Common;
26 session_start();
27 if (isset($_SESSION['pid']) && isset($_SESSION['patient_portal_onsite_two'])) {
28 $pid = $_SESSION['pid'];
29 $ignoreAuth = true;
30 require_once(dirname(__FILE__) . "/../../interface/globals.php");
31 define('IS_DASHBOARD', false);
32 define('IS_PORTAL', $_SESSION['pid']);
33 } else {
34 session_destroy();
35 $ignoreAuth = false;
36 require_once(dirname(__FILE__) . "/../../interface/globals.php");
37 if (! isset($_SESSION['authUserID'])) {
38 $landingpage = "index.php";
39 header('Location: '.$landingpage);
40 exit;
42 $admin = sqlQueryNoLog(
43 "SELECT CONCAT(users.fname,' ',users.lname) as user_name FROM users WHERE id = ?",
44 array($_SESSION['authUserID'])
46 define('ADMIN_USERNAME', $admin['user_name']);
47 define('IS_DASHBOARD', $_SESSION['authUser']);
48 define('IS_PORTAL', false);
49 $_SERVER[REMOTE_ADDR] = 'admin::' . $_SERVER[REMOTE_ADDR];
53 define('C_USER', IS_PORTAL ? IS_PORTAL : IS_DASHBOARD);
55 if (isset($_REQUEST['fullscreen'])) {
56 $_SESSION['whereto'] = 'messagespanel';
57 define('IS_FULLSCREEN', true);
58 } else {
59 define('IS_FULLSCREEN', false);
62 define('CHAT_HISTORY', '150');
63 define('CHAT_ONLINE_RANGE', '1');
64 define('ADMIN_USERNAME_PREFIX', 'adm_');
66 abstract class Model
68 public $db;
70 public function __construct()
72 //$this->db = new \mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
76 abstract class Controller
78 private $_request, $_response, $_query, $_post, $_server, $_cookies, $_session;
79 protected $_currentAction, $_defaultModel;
81 const ACTION_POSTFIX = 'Action';
82 const ACTION_DEFAULT = 'indexAction';
84 public function __construct()
86 $this->_request = &$_REQUEST;
87 $this->_query = &$_GET;
88 $this->_post = &$_POST;
89 $this->_server = &$_SERVER;
90 $this->_cookies = &$_COOKIE;
91 $this->_session = &$_SESSION;
92 $this->init();
95 public function init()
97 $this->dispatchActions();
98 $this->render();
101 public function dispatchActions()
103 $action = $this->getQuery('action');
104 if ($action && $action .= self::ACTION_POSTFIX) {
105 if (method_exists($this, $action)) {
106 $this->setResponse(
107 call_user_func(array($this, $action), array())
109 } else {
110 $this->setHeader("HTTP/1.0 404 Not Found");
112 } else {
113 $this->setResponse(
114 call_user_func(array($this, self::ACTION_DEFAULT), array())
118 return $this->_response;
121 public function render()
123 if ($this->_response) {
124 if (is_scalar($this->_response)) {
125 echo $this->_response;
126 } else {
127 throw new \Exception('Response content must be scalar');
130 exit;
134 public function indexAction()
136 return null;
139 public function setResponse($content)
141 $this->_response = $content;
144 public function setHeader($params)
146 if (! headers_sent()) {
147 if (is_scalar($params)) {
148 header($params);
149 } else {
150 foreach ($params as $key => $value) {
151 header(sprintf('%s: %s', $key, $value));
156 return $this;
159 public function setModel($namespace)
161 $this->_defaultModel = $namespace;
162 return $this;
165 public function setSession($key, $value)
167 $_SESSION[$key] = $value;
168 return $this;
171 public function setCookie($key, $value, $seconds = 3600)
173 $this->_cookies[$key] = $value;
174 if (! headers_sent()) {
175 setcookie($key, $value, time() + $seconds);
176 return $this;
180 public function getRequest($param = null, $default = null)
182 if ($param) {
183 return isset($this->_request[$param]) ?
184 $this->_request[$param] : $default;
187 return $this->_request;
190 public function getQuery($param = null, $default = null)
192 if ($param) {
193 return isset($this->_query[$param]) ?
194 $this->_query[$param] : $default;
197 return $this->_query;
200 public function getPost($param = null, $default = null)
202 if ($param) {
203 return isset($this->_post[$param]) ?
204 $this->_post[$param] : $default;
207 return $this->_post;
210 public function getServer($param = null, $default = null)
212 if ($param) {
213 return isset($this->_server[$param]) ?
214 $this->_server[$param] : $default;
217 return $this->_server;
220 public function getSession($param = null, $default = null)
222 if ($param) {
223 return isset($this->_session[$param]) ?
224 $this->_session[$param] : $default;
227 return $this->_session;
230 public function getCookie($param = null, $default = null)
232 if ($param) {
233 return isset($this->_cookies[$param]) ?
234 $this->_cookies[$param] : $default;
237 return $this->_cookies;
240 public function getUser()
242 return $this->_session['ptName'] ? $this->_session['ptName'] : $this->_session['authUser'];
244 public function getIsPortal()
246 return IS_PORTAL;
248 public function getIsFullScreen()
250 return IS_FULLSCREEN;
252 public function getModel()
254 if ($this->_defaultModel && class_exists($this->_defaultModel)) {
255 return new $this->_defaultModel;
259 public function sanitize($string, $quotes = ENT_QUOTES, $charset = 'utf-8')
261 return htmlentities($string, $quotes, $charset);
265 abstract class Helper
270 namespace SMA_Msg;
272 // @codingStandardsIgnoreStart
273 use SMA_Common;
274 // @codingStandardsIgnoreEnd
275 class Model extends SMA_Common\Model
277 public function getAuthUsers()
279 $resultpd = array();
280 $result = array();
281 if (!IS_PORTAL) {
282 $query = "SELECT patient_data.pid as recip_id, Concat_Ws(' ', patient_data.fname, patient_data.lname) as username FROM patient_data " .
283 "LEFT JOIN patient_access_onsite pao ON pao.pid = patient_data.pid " .
284 "WHERE patient_data.pid = pao.pid AND pao.portal_pwd_status = 1";
285 $response = sqlStatementNoLog($query);
286 while ($row = sqlFetchArray($response)) {
287 $resultpd[] = $row;
290 if (IS_PORTAL) {
291 $query = "SELECT users.username as recip_id, users.authorized as dash, CONCAT(users.fname,' ',users.lname) as username " .
292 "FROM users WHERE active = 1 AND username > ''";
293 $response = sqlStatementNoLog($query);
295 while ($row = sqlFetchArray($response)) {
296 $result[] = $row;
299 $all = array_merge($result, $resultpd);
301 return json_encode($all);
303 public function getMessages($limit = CHAT_HISTORY, $reverse = true)
305 $response = sqlStatementNoLog("(SELECT * FROM onsite_messages
306 ORDER BY `date` DESC LIMIT {$limit}) ORDER BY `date` ASC");
308 $result = array();
309 while ($row = sqlFetchArray($response)) {
310 if (IS_PORTAL || IS_DASHBOARD) {
311 $u = json_decode($row['recip_id'], true);
312 if (!is_array($u)) {
313 continue;
316 if ((in_array(C_USER, $u)) || $row['sender_id'] == C_USER) {
317 $result[] = $row; // only current patient messages
319 } else {
320 $result[] = $row; // admin gets all
324 return $result;
327 public function addMessage($username, $message, $ip, $senderid = 0, $recipid = '')
329 return sqlQueryNoLog("INSERT INTO onsite_messages VALUES (NULL, ?, ?, ?, NOW(), ?, ?)", array($username,$message,$ip,$senderid,$recipid));
332 public function removeMessages()
334 return sqlQueryNoLog("TRUNCATE TABLE onsite_messages");
337 public function removeOldMessages($limit = CHAT_HISTORY)
339 /* @todo Patched out to replace with soft delete. Besides this query won't work with current ado(or any) */
340 /* return sqlStatementNoLog("DELETE FROM onsite_messages
341 WHERE id NOT IN (SELECT id FROM onsite_messages
342 ORDER BY date DESC LIMIT {$limit})"); */
345 public function getOnline($count = true, $timeRange = CHAT_ONLINE_RANGE)
347 if ($count) {
348 $response = sqlStatementNoLog("SELECT count(*) as total FROM onsite_online");
349 return sqlFetchArray($response);
352 $response = sqlStatementNoLog("SELECT * FROM onsite_online");
353 $result = array();
354 while ($row = sqlFetchArray($response)) {
355 $result[] = $row;
358 return $result;
361 public function updateOnline($hash, $ip, $username = '', $userid = 0)
363 return sqlStatementNoLog("REPLACE INTO onsite_online
364 VALUES ( ?, ?, NOW(), ?, ? )", array($hash, $ip, $username, $userid)) or die(mysql_error());
367 public function clearOffline($timeRange = CHAT_ONLINE_RANGE)
369 return sqlStatementNoLog("DELETE FROM onsite_online
370 WHERE last_update <= (NOW() - INTERVAL {$timeRange} MINUTE)");
373 public function __destruct()
378 class Controller extends SMA_Common\Controller
380 protected $_model;
382 public function __construct()
384 $this->setModel('SMA_Msg\Model');
385 parent::__construct();
388 public function indexAction()
391 public function authusersAction()
393 return $this->getModel()->getAuthUsers(true);
395 public function listAction()
397 $this->setHeader(array('Content-Type' => 'application/json'));
398 $messages = $this->getModel()->getMessages();
399 foreach ($messages as &$message) {
400 $message['me'] = C_USER === $message['sender_id']; // $this->getServer('REMOTE_ADDR') === $message['ip'];
403 return json_encode($messages);
406 public function saveAction()
408 $username = $this->getPost('username');
409 $message = $this->getPost('message');
410 $ip = $this->getServer('REMOTE_ADDR');
411 $this->setCookie('username', $username, 9999 * 9999);
412 $recipid = $this->getPost('recip_id');
414 if (IS_PORTAL) {
415 $senderid = IS_PORTAL;
416 } else {
417 $senderid = IS_DASHBOARD;
420 $result = array('success' => false);
421 if ($username && $message) {
422 $cleanUsername = preg_replace('/^'.ADMIN_USERNAME_PREFIX.'/', '', $username);
423 $result = array(
424 'success' => $this->getModel()->addMessage($cleanUsername, $message, $ip, $senderid, $recipid)
428 if ($this->_isAdmin($username)) {
429 $this->_parseAdminCommand($message);
432 $this->setHeader(array('Content-Type' => 'application/json'));
433 return json_encode($result);
436 private function _isAdmin($username)
438 return IS_DASHBOARD?true:false;
439 //return preg_match('/^'.ADMIN_USERNAME_PREFIX.'/', $username);
442 private function _parseAdminCommand($message)
444 if (strpos($message, '/clear') !== false) {
445 $this->getModel()->removeMessages();
446 return true;
449 if (strpos($message, '/online') !== false) {
450 $online = $this->getModel()->getOnline(false);
451 $ipArr = array();
452 foreach ($online as $item) {
453 $ipArr[] = $item->ip;
456 $message = 'Online: ' . implode(", ", $ipArr);
457 $this->getModel()->addMessage('Admin Command', $message, '0.0.0.0');
458 return true;
462 private function _getMyUniqueHash()
464 $unique = $this->getServer('REMOTE_ADDR');
465 $unique .= $this->getServer('HTTP_USER_AGENT');
466 $unique .= $this->getServer('HTTP_ACCEPT_LANGUAGE');
467 $unique .= C_USER;
468 return md5($unique);
471 public function pingAction()
473 $ip = $this->getServer('REMOTE_ADDR');
474 $hash = $this->_getMyUniqueHash();
475 $user = $this->getRequest('username', 'No Username');
476 if ($user == 'currentol') {
477 $onlines = $this->getModel()->getOnline(false);
478 $this->setHeader(array('Content-Type' => 'application/json'));
479 return json_encode($onlines);
482 if (IS_PORTAL) {
483 $userid = IS_PORTAL;
484 } else {
485 $userid = IS_DASHBOARD;
488 $this->getModel()->updateOnline($hash, $ip, $user, $userid);
489 $this->getModel()->clearOffline();
490 // $this->getModel()->removeOldMessages(); // @todo For soft delete when I decide. DO NOT REMOVE
492 $onlines = $this->getModel()->getOnline();
494 $this->setHeader(array('Content-Type' => 'application/json'));
495 return json_encode($onlines);
499 $msgApp = new Controller();
501 <!doctype html>
502 <html ng-app="MsgApp">
503 <head>
504 <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
505 <meta charset="utf-8">
507 <title><?php echo xlt('Secure Patient Chat'); ?></title>
508 <meta name="author" content="Jerry Padgett sjpadgett{{at}} gmail {{dot}} com">
510 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-1-11-3/dist/jquery.js"></script>
512 <link href="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
513 <?php if ($_SESSION['language_direction'] == 'rtl') { ?>
514 <link href="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-rtl/dist/css/bootstrap-rtl.min.css" rel="stylesheet" type="text/css" />
515 <?php } ?>
517 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap/dist/js/bootstrap.min.js"></script>
519 <link rel="stylesheet" href="<?php echo $GLOBALS['assets_static_relative']; ?>/summernote/dist/summernote.css" />
520 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/summernote/dist/summernote.js"></script>
522 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/angular/angular.min.js"></script>
523 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/angular-summernote/dist/angular-summernote.js"></script>
524 <script type='text/javascript' src="<?php echo $GLOBALS['assets_static_relative']; ?>/angular-sanitize/angular-sanitize.min.js"></script>
525 <script src='<?php echo $GLOBALS['assets_static_relative']; ?>/checklist-model/checklist-model.js'></script>
527 </head>
528 <script type="text/javascript">
529 (function() {
530 var MsgApp = angular.module('MsgApp',['ngSanitize','summernote',"checklist-model"]);
531 MsgApp.config(function( $compileProvider ) {
532 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|file|ftp|blob):|data:image\//);
535 MsgApp.directive('ngEnter', function () {
536 return function (scope, element, attrs) {
537 element.bind("keydown keypress", function (event) {
538 if (event.which === 13) {
539 scope.$apply(function (){
540 scope.$eval(attrs.ngEnter);
542 event.preventDefault();
547 MsgApp.directive('tooltip', function(e){
548 return {
549 restrict: 'A',
550 link: function(scope, element, attrs){
551 $(element).hover(function(){
552 $(element).tooltip('show');
553 }, function(){
554 $(element).tooltip('hide');
559 MsgApp.filter('unique', function() {
560 return function(collection, keyname) {
561 var output = [],
562 keys = [];
563 angular.forEach(collection, function(item) {
564 var key = item[keyname];
565 if(keys.indexOf(key) === -1) {
566 keys.push(key);
567 output.push(item);
570 return output;
574 MsgApp.controller('MsgAppCtrl', ['$scope', '$http', '$filter', function($scope, $http, $filter) {
575 $scope.urlListMessages = '?action=list'; // actions for restful
576 $scope.urlSaveMessage = '?action=save';
577 $scope.urlListOnlines = '?action=ping';
578 $scope.urlGetAuthUsers = '?action=authusers';
580 $scope.pidMessages = null;
581 $scope.pidPingServer = null;
583 $scope.beep = new Audio('beep.ogg'); // you've got mail!!!! really just a beep
584 $scope.messages = [];
585 $scope.online = null;
586 $scope.lastMessageId = null;
587 $scope.historyFromId = null;
588 $scope.onlines = []; // all online users id and ip's
589 $scope.user = "<?php echo $_SESSION['ptName'] ? $_SESSION['ptName'] : ADMIN_USERNAME;?>";// current user - dashboard user is from session authUserID
590 $scope.userid = "<?php echo IS_PORTAL ? $_SESSION['pid'] : $_SESSION['authUser'];?>";
591 $scope.isPortal = "<?php echo IS_PORTAL;?>";
592 $scope.isFullScreen = "<?php echo IS_FULLSCREEN; ?>";
593 $scope.pusers = []; // selected recipients for chat
594 $scope.chatusers = []; // authorize chat recipients for dashboard user
595 $scope.noRecipError = '<?php echo xla("Please Select a Recipient for Message.") ?>';
596 $scope.me = {
597 username: $scope.user,
598 message: null,
599 sender_id: $scope.userid,
600 recip_id: 0
602 $scope.options = {
603 height: 200,
604 focus: true,
605 placeholder: 'Start typing your message...',
606 //direction: 'rtl',
607 toolbar: [
608 ['style', ['bold', 'italic', 'underline', 'clear']],
609 ['fontsize', ['fontsize']],
610 ['color', ['color']],
611 ['para', ['ul', 'ol', 'paragraph']],
612 ['insert', ['link','picture', 'video', 'hr']],
613 ['view', ['fullscreen', 'codeview']]
616 $scope.checkAll = function() {
617 $scope.pusers = [];
618 $scope.pusers = $scope.chatusers.map(function(item) { return item.recip_id; });
619 $scope.getAuthUsers();
621 $scope.uncheckAll = function() {
622 $scope.pusers = [];
623 $scope.getAuthUsers();
625 $scope.makeCurrent = function(sel) {
626 if( !sel.me ){
627 $scope.pusers.splice(0, $scope.pusers.length);
628 $scope.pusers.push(sel.sender_id);
631 $scope.pageTitleNotificator = {
632 vars: {
633 originalTitle: window.document.title,
634 interval: null,
635 status: 0
637 on: function(title, intervalSpeed) {
638 var self = this;
639 if (! self.vars.status) {
640 self.vars.interval = window.setInterval(function() {
641 window.document.title = (self.vars.originalTitle == window.document.title) ?
642 title : self.vars.originalTitle;
643 }, intervalSpeed || 500);
644 self.vars.status = 1;
647 off: function() {
648 window.clearInterval(this.vars.interval);
649 window.document.title = this.vars.originalTitle;
650 this.vars.status = 0;
654 $scope.editmsg = function() {
655 $('.summernote').summernote();
658 $scope.saveedit = function() {
659 var makrup = $('.summernote').summernote('code');
660 $scope.me.message = makrup;
661 $scope.saveMessage();
662 $('.summernote').summernote('code', ''); //add this options to reset editor or not-default is persistent content
663 //$('.summernote').summernote('destroy');
666 $scope.saveMessage = function(form, callback) {
667 $scope.me.recip_id = JSON.stringify(angular.copy($scope.pusers));
668 var data = $.param($scope.me);
669 if (! ($scope.me.username && $scope.me.username.trim())) {
670 return $scope.openModal();
672 if (! ($scope.me.message && $scope.me.message.trim() &&
673 $scope.me.username && $scope.me.username.trim())) {
674 return;
676 if($scope.me.recip_id == "[]") {
677 alert($scope.noRecipError);
678 return;
680 $scope.me.message = '';
681 return $http({
682 method: 'POST',
683 url: $scope.urlSaveMessage,
684 data: data,
685 headers: {'Content-Type': 'application/x-www-form-urlencoded'}
686 }).success(function(data) {
687 $scope.listMessages(true);
691 $scope.replaceShortcodes = function(message) {
692 var msg = '';
693 msg = message.toString().replace(/(\[img])(.*)(\[\/img])/, "<img class='img-responsive' src='$2' />");
694 msg = msg.toString().replace(/(\[url])(.*)(\[\/url])/, "<a href='$2'>$2</a>");
695 msg = message.toString().replace("<img ", "<img class='img-responsive' ");
696 return msg;
699 $scope.notifyLastMessage = function() {
700 if (typeof window.Notification === 'undefined') {
701 return;
703 window.Notification.requestPermission(function (permission) {
704 var lastMessage = $scope.getLastMessage();
705 if (permission == 'granted' && lastMessage && lastMessage.username) {
706 var notify = new window.Notification('Message notification from ' + lastMessage.username + ' : ', {
707 body: 'New message' //lastMessage.message
709 notify.onclick = function() {
710 window.focus();
712 notify.onclose = function() {
713 $scope.pageTitleNotificator.off();
715 var timmer = setInterval(function() {
716 notify && notify.close();
717 typeof timmer !== 'undefined' && window.clearInterval(timmer);
718 }, 60000);
723 $scope.getLastMessage = function() {
724 return $scope.messages[$scope.messages.length - 1];
727 $scope.listMessages = function(wasListingForMySubmission) {
728 return $http.post($scope.urlListMessages, {}).success(function(data) {
729 $scope.messages = [];
730 angular.forEach(data, function(message) {
731 message.message = $scope.replaceShortcodes(message.message);
732 $scope.messages.push(message);
735 var lastMessage = $scope.getLastMessage();
736 var lastMessageId = lastMessage && lastMessage.id;
738 if ($scope.lastMessageId !== lastMessageId) {
739 $scope.onNewMessage(wasListingForMySubmission);
741 $scope.lastMessageId = lastMessageId;
742 if($scope.pusers === ''){ // refresh current in chat list.
743 angular.forEach($filter('unique')($scope.messages,'sender_id'), function(m,k){
744 var flg = false;
745 angular.forEach($scope.pusers, function(id) {
746 if(id === m.sender_id){ flg = true; }
748 if(!flg) $scope.pusers.push(m.sender_id);
751 $scope.getOnlines();
755 $scope.onNewMessage = function(wasListingForMySubmission) {
756 if ($scope.lastMessageId && !wasListingForMySubmission) {
757 $scope.playAudio();
758 $scope.pageTitleNotificator.on('New message');
759 $scope.notifyLastMessage();
761 $scope.scrollDown();
762 window.addEventListener('focus', function() {
763 $scope.pageTitleNotificator.off();
767 $scope.getAuthUsers = function() {
768 $scope.chatusers = [];
769 return $http.post($scope.urlGetAuthUsers, {}).success(function(data) {
770 $scope.chatusers = data;
774 $scope.pingServer = function(msgItem) {
775 return $http.post($scope.urlListOnlines+'&username='+$scope.user, {}).success(function(data) {
776 $scope.online = data;
781 $scope.getOnlines = function() {
782 return $http.post($scope.urlListOnlines+'&username=currentol', {}).success(function(data) {
783 $scope.onlines = data;
787 $scope.init = function() {
788 $scope.listMessages();
789 $scope.pidMessages = window.setInterval($scope.listMessages, 6000);
790 $scope.pidPingServer = window.setInterval($scope.pingServer, 10000);
791 $scope.getAuthUsers();
792 $("#popeditor").on("show.bs.modal", function() {
793 var height = $(window).height() - 200;
794 $(this).find(".modal-body").css("max-height", height);
798 $scope.scrollDown = function() {
799 var pidScroll;
800 pidScroll = window.setInterval(function() {
801 $('.direct-chat-messages').scrollTop(window.Number.MAX_SAFE_INTEGER * 0.001);
802 window.clearInterval(pidScroll);
803 }, 100);
806 $scope.clearHistory = function() {
807 var lastMessage = $scope.getLastMessage();
808 var lastMessageId = lastMessage && lastMessage.id;
809 lastMessageId = (lastMessageId-1 >= 2) ? lastMessageId -1 : lastMessageId;
810 lastMessageId && ($scope.historyFromId = lastMessageId);
813 $scope.openModal = function(e) {
814 var mi = $('#popeditor').modal({backdrop: "static"});
815 //$scope.editmsg();
818 $scope.playAudio = function() {
819 $scope.beep && $scope.beep.play();
822 $scope.renderMessageBody = function(html)
824 return html;
826 $scope.init();
827 }]);
828 })();
829 </script>
830 <style>
831 .direct-chat-text {
832 border-radius:5px;
833 position:relative;
834 padding:5px 10px;
835 background:#FBFBFB;
836 border:1px solid #6a6a6a;
837 margin:5px 0 0 50px;
838 color:#444;
840 .direct-chat-msg,.direct-chat-text {
841 display:block;
842 word-wrap: break-word;
844 .direct-chat-img {
845 border-radius:50%;
846 float:left;
847 width:40px;
848 height:40px;
850 .direct-chat-info {
851 display:block;
852 margin-bottom:2px;
853 font-size:12px;
855 .direct-chat-msg {
856 margin-bottom:5px;
858 .direct-chat-messages,.direct-chat-contacts {
859 -webkit-transition:-webkit-transform .5s ease-in-out;
860 -moz-transition:-moz-transform .5s ease-in-out;
861 -o-transition:-o-transform .5s ease-in-out;
862 transition:transform .5s ease-in-out;
864 .direct-chat-messages {
865 -webkit-transform:translate(0,0);
866 -ms-transform:translate(0,0);
867 -o-transform:translate(0,0);
868 transform:translate(0,0);
869 padding: 5px;
870 height: calc(100vh - 175px);
871 /* height: 400px; */
872 /*height:100%; */
873 overflow:auto;
874 word-wrap: break-word;
876 .direct-chat-text:before {
877 border-width:6px;
878 margin-top:-6px;
880 .direct-chat-text:after {
881 border-width:5px;
882 margin-top:-5px;
884 .direct-chat-text:after,.direct-chat-text:before {
885 position:absolute;
886 right:100%;
887 top:15px;
888 border:solid rgba(0,0,0,0);
889 border-right-color:#D2D6DE;
890 content:' ';
891 height:0;
892 width:0;
893 pointer-events:none;
895 .direct-chat-warning .right>.direct-chat-text {
896 background: rgba(251, 255, 178, 0.34);
897 border-color: #f30d1b;
898 color:#000;
900 .right .direct-chat-text {
901 margin-right:50px;
902 margin-left:0;
904 .direct-chat-warning .right>.direct-chat-text:after,
905 .direct-chat-warning .right>.direct-chat-text:before {
906 border-left-color:#F39C12;
908 .right .direct-chat-text:after,.right .direct-chat-text:before {
909 right:auto;
910 left:100%;
911 border-right-color:rgba(0,0,0,0);
912 border-left-color:#D2D6DE;
914 .right .direct-chat-img {
915 float:right;
917 .box-footer {
918 border-top-left-radius:0;
919 border-top-right-radius:0;
920 border-bottom-right-radius:3px;
921 border-bottom-left-radius:3px;
922 border-top:1px solid #F4F4F4;
923 padding:10px 0;
924 background-color:#FFF;
926 .direct-chat-name {
927 font-weight:600;
929 .box-footer form {
930 margin-bottom:10px;
932 input,button,.alert,.modal-content {
933 border-radius: 0!important;
935 .ml10 {
936 margin-left:10px;
938 .ml5 {
939 margin-left:5px;
941 .sidebar{
942 background-color: ghostwhite;
943 height:100%;
944 margin-top:5px;
945 margin-right:0;
946 padding-right:5px;
947 /*max-height: 730px;*/
948 height: calc(100vh - 100px);
949 overflow: auto;
951 .rtsidebar{
952 background-color: ghostwhite;
953 height:100%;
954 margin-top:5px;
955 margin-right:0;
956 height: calc(100vh - 100px);
957 overflow: auto;
959 .fixed-panel{
960 height: 100%;
961 padding: 5px 5px 0 5px;
963 h5 {
964 font-size:16px !important;
966 label {display: block;}
967 legend{
968 font-size:14px;
969 margin-bottom:2px;
970 background:#fff;
972 .modal.modal-wide .modal-dialog {
973 width: 75%;
975 .modal-wide .modal-body {
976 overflow-y: auto;
978 </style>
980 <body ng-controller="MsgAppCtrl">
981 <div class="container">
982 <!-- <h2 class="hidden-xs">Secure Chat</h2> -->
983 <div class="row">
984 <div class="col-md-2 sidebar">
985 <h5><span class="label label-default"><?php echo xlt('Current Recipients'); ?></span></h5>
986 <label ng-repeat="user in chatusers | unique : 'username'" ng-if="pusers.indexOf(user.recip_id) !== -1 && user.recip_id != me.sender_id">
987 <input type="checkbox" data-checklist-model="pusers" data-checklist-value="user.recip_id"> {{user.username}}
988 </label>
989 <h5><span class="label label-default"><?php echo xlt('Available Recipients'); ?></span></h5>
990 <span>
991 <button id="chkall" class="btn btn-xs btn-success" ng-show="!isPortal" ng-click="checkAll()" type="button"><?php echo xlt('All'); ?></button>
992 <button id="chknone" class="btn btn-xs btn-success" ng-show="!isPortal" ng-click="uncheckAll()" type="button"><?php echo xlt('None'); ?></button>
993 </span>
994 <label ng-repeat="user in chatusers | unique : 'username'" ng-show="!isPortal || (isPortal && user.dash)">
995 <input type="checkbox" data-checklist-model="pusers" data-checklist-value="user.recip_id"> {{user.username}}
996 </label>
997 </div>
998 <div class="col-md-8 fixed-panel">
999 <div class="panel direct-chat direct-chat-warning">
1000 <div class="panel-heading">
1001 <div class="clearfix">
1002 <a class="btn btn-sm btn-primary ml10" href=""
1003 data-toggle="modal" data-target="#clear-history"><?php echo xlt('Clear history'); ?></a>
1004 <a class="btn btn-sm btn-success pull-left ml10" href="./../patient/provider" ng-show="!isPortal"><?php echo xlt('Home'); ?></a>
1005 <a class="btn btn-sm btn-success pull-left ml10" href="./../home.php" ng-show="isFullScreen"><?php echo xlt('Home'); ?></a>
1006 </div>
1007 </div>
1008 <div class="panel-body">
1009 <div class="direct-chat-messages">
1010 <div class="direct-chat-msg" ng-repeat="message in messages" ng-if="historyFromId < message.id" ng-class="{'right':!message.me}">
1011 <div class="direct-chat-info clearfix">
1012 <span class="direct-chat-name" ng-class="{'pull-left':message.me,'pull-right':!message.me}">{{message.username }}</span>
1013 <span class="direct-chat-timestamp " ng-class="{'pull-left':!message.me,'pull-right':message.me}">{{message.date }}</span>
1014 </div>
1015 <i class="direct-chat-img glyphicon glyphicon-hand-left"
1016 style="cursor: pointer;font-size:24px" ng-show="!message.me"
1017 ng-click="makeCurrent(message)"
1018 title="<?php echo xla('Click to activate and send to this recipient.'); ?>"></i>
1019 <i class="direct-chat-img glyphicon glyphicon-hand-right"
1020 style="cursor: pointer;font-size:24px" ng-show="message.me"
1021 ng-click="makeCurrent(message)"
1022 title="<?php echo xla('Click to activate and send to this recipient.'); ?>"></i>
1024 <div class="direct-chat-text right">
1025 <div style="padding-left: 0px; padding-right: 0px;"
1026 title="<?php echo xla('Click to activate and send to this recipient.'); ?>"
1027 ng-click="makeCurrent(message)"
1028 ng-bind-html=renderMessageBody(message.message)></div>
1029 </div>
1030 </div>
1031 </div>
1032 <div class="panel-footer box-footer-hide">
1033 <form id='msgfrm' ng-submit="saveMessage()">
1034 <div class="input-group">
1035 <input type="text" placeholder="Type message..." id="msgedit" autofocus="autofocus"
1036 class="form-control" ng-model="me.message" ng-enter="saveMessage()">
1037 <span class="input-group-btn">
1038 <button type="submit" class="btn btn-danger btn-flat"><?php echo xlt('Send'); ?></button>
1039 <button type="button" class="btn btn-success btn-flat" ng-click="openModal(event)"><?php echo xlt('Edit'); ?></button>
1040 </span>
1041 </div>
1042 </form>
1043 </div>
1044 </div>
1045 </div>
1046 </div>
1047 <div class="col-md-2 rtsidebar">
1048 <h5><span class="label label-default"><?php echo xlt('Whose Online'); ?> : {{ online.total || '0' }}</span>
1049 </h5>
1050 <label ng-repeat="ol in onlines | unique : 'username'">
1051 <input type="checkbox" data-checklist-model="onlines" data-checklist-value="ol"> {{ol.username}}
1052 </label>
1053 </div>
1054 </div>
1056 <div class="modal modal-wide fade" id="popeditor">
1057 <div class="modal-dialog modal-lg">
1058 <div class="modal-content">
1059 <form>
1060 <div class="modal-header">
1061 <button type="button" class="close" data-dismiss="modal">
1062 <span aria-hidden="true">&times;</span>
1063 <span class="sr-only"><?php echo xlt('Close'); ?></span>
1064 </button>
1065 <h4 class="modal-title"><?php echo xlt('Style your messsage and/or add Image/Video'); ?></h4>
1066 </div>
1067 <div class="modal-body">
1068 <summernote config="options"></summernote>
1069 </div>
1070 <div class="modal-footer">
1071 <button type="button" class="btn btn-sm" data-dismiss="modal"><?php echo xlt('Dismiss'); ?></button>
1072 <button type="button" class="btn btn-success" data-dismiss="modal" ng-click="saveedit()"><?php echo xlt('Send It'); ?></button>
1073 </div>
1074 </form>
1075 </div>
1076 </div>
1077 </div>
1078 <div class="modal" id="clear-history">
1079 <div class="modal-dialog">
1080 <div class="modal-content">
1081 <form>
1082 <div class="modal-header">
1083 <button type="button" class="close" data-dismiss="modal">
1084 <span aria-hidden="true">&times;</span>
1085 <span class="sr-only"><?php echo xlt('Close'); ?></span>
1086 </button>
1087 <h4 class="modal-title"><?php echo xlt('Chat history'); ?></h4>
1088 </div>
1089 <div class="modal-body">
1090 <label class="radio"><?php echo xlt('Are you sure to clear chat history?'); ?></label>
1091 </div>
1092 <div class="modal-footer">
1093 <button type="button" class="btn btn-sm btn-default" data-dismiss="modal"><?php echo xlt('Cancel'); ?></button>
1094 <button type="button" class="btn btn-sm btn-primary" data-dismiss="modal" ng-click="clearHistory()"><?php echo xlt('Accept'); ?></button>
1095 </div>
1096 </form>
1097 </div>
1098 </div>
1099 </div>
1101 </body>
1102 </html>