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/>.
20 * @author Jerry Padgett <sjpadgett@gmail.com>
21 * @link http://www.open-emr.org
27 if (isset($_SESSION['pid']) && isset($_SESSION['patient_portal_onsite_two'])) {
28 $pid = $_SESSION['pid'];
30 require_once(dirname(__FILE__
) . "/../../interface/globals.php");
31 define('IS_DASHBOARD', false);
32 define('IS_PORTAL', $_SESSION['pid']);
36 require_once(dirname(__FILE__
) . "/../../interface/globals.php");
37 if (! isset($_SESSION['authUserID'])) {
38 $landingpage = "index.php";
39 header('Location: '.$landingpage);
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);
59 define('IS_FULLSCREEN', false);
62 define('CHAT_HISTORY', '150');
63 define('CHAT_ONLINE_RANGE', '1');
64 define('ADMIN_USERNAME_PREFIX', 'adm_');
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;
95 public function init()
97 $this->dispatchActions();
101 public function dispatchActions()
103 $action = $this->getQuery('action');
104 if ($action && $action .= self
::ACTION_POSTFIX
) {
105 if (method_exists($this, $action)) {
107 call_user_func(array($this, $action), array())
110 $this->setHeader("HTTP/1.0 404 Not Found");
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
;
127 throw new \
Exception('Response content must be scalar');
134 public function indexAction()
139 public function setResponse($content)
141 $this->_response
= $content;
144 public function setHeader($params)
146 if (! headers_sent()) {
147 if (is_scalar($params)) {
150 foreach ($params as $key => $value) {
151 header(sprintf('%s: %s', $key, $value));
159 public function setModel($namespace)
161 $this->_defaultModel
= $namespace;
165 public function setSession($key, $value)
167 $_SESSION[$key] = $value;
171 public function setCookie($key, $value, $seconds = 3600)
173 $this->_cookies
[$key] = $value;
174 if (! headers_sent()) {
175 setcookie($key, $value, time() +
$seconds);
180 public function getRequest($param = null, $default = null)
183 return isset($this->_request
[$param]) ?
184 $this->_request
[$param] : $default;
187 return $this->_request
;
190 public function getQuery($param = null, $default = null)
193 return isset($this->_query
[$param]) ?
194 $this->_query
[$param] : $default;
197 return $this->_query
;
200 public function getPost($param = null, $default = null)
203 return isset($this->_post
[$param]) ?
204 $this->_post
[$param] : $default;
210 public function getServer($param = null, $default = null)
213 return isset($this->_server
[$param]) ?
214 $this->_server
[$param] : $default;
217 return $this->_server
;
220 public function getSession($param = null, $default = null)
223 return isset($this->_session
[$param]) ?
224 $this->_session
[$param] : $default;
227 return $this->_session
;
230 public function getCookie($param = null, $default = null)
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()
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
272 // @codingStandardsIgnoreStart
274 // @codingStandardsIgnoreEnd
275 class Model
extends SMA_Common\Model
277 public function getAuthUsers()
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)) {
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)) {
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");
309 while ($row = sqlFetchArray($response)) {
310 if (IS_PORTAL || IS_DASHBOARD
) {
311 $u = json_decode($row['recip_id'], true);
316 if ((in_array(C_USER
, $u)) ||
$row['sender_id'] == C_USER
) {
317 $result[] = $row; // only current patient messages
320 $result[] = $row; // admin gets all
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
)
348 $response = sqlStatementNoLog("SELECT count(*) as total FROM onsite_online");
349 return sqlFetchArray($response);
352 $response = sqlStatementNoLog("SELECT * FROM onsite_online");
354 while ($row = sqlFetchArray($response)) {
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
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');
415 $senderid = IS_PORTAL
;
417 $senderid = IS_DASHBOARD
;
420 $result = array('success' => false);
421 if ($username && $message) {
422 $cleanUsername = preg_replace('/^'.ADMIN_USERNAME_PREFIX
.'/', '', $username);
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();
449 if (strpos($message, '/online') !== false) {
450 $online = $this->getModel()->getOnline(false);
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');
462 private function _getMyUniqueHash()
464 $unique = $this->getServer('REMOTE_ADDR');
465 $unique .= $this->getServer('HTTP_USER_AGENT');
466 $unique .= $this->getServer('HTTP_ACCEPT_LANGUAGE');
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);
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();
502 <html ng
-app
="MsgApp">
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" />
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
>
528 <script type
="text/javascript">
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
){
550 link
: function(scope
, element
, attrs
){
551 $
(element
).hover(function(){
552 $
(element
).tooltip('show');
554 $
(element
).tooltip('hide');
559 MsgApp
.filter('unique', function() {
560 return function(collection
, keyname
) {
563 angular
.forEach(collection
, function(item
) {
564 var key
= item
[keyname
];
565 if(keys
.indexOf(key
) === -1) {
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.") ?>';
597 username
: $scope.user
,
599 sender_id
: $scope.userid
,
605 placeholder
: 'Start typing your message...',
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() {
618 $scope.pusers
= $scope.chatusers
.map(function(item
) { return item
.recip_id
; });
619 $scope.getAuthUsers();
621 $scope.uncheckAll
= function() {
623 $scope.getAuthUsers();
625 $scope.makeCurrent
= function(sel
) {
627 $scope.pusers
.splice(0, $scope.pusers
.length
);
628 $scope.pusers
.push(sel
.sender_id
);
631 $scope.pageTitleNotificator
= {
633 originalTitle
: window
.document
.title
,
637 on
: function(title
, intervalSpeed
) {
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;
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())) {
676 if($scope.me
.recip_id
== "[]") {
677 alert($scope.noRecipError
);
680 $scope.me
.message
= '';
683 url
: $scope.urlSaveMessage
,
685 headers
: {'Content-Type': 'application/x-www-form-urlencoded'}
686 }).success(function(data
) {
687 $scope.listMessages(true);
691 $scope.replaceShortcodes
= function(message
) {
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' ");
699 $scope.notifyLastMessage
= function() {
700 if (typeof window
.Notification
=== 'undefined') {
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() {
712 notify
.onclose
= function() {
713 $scope.pageTitleNotificator
.off();
715 var timmer
= setInterval(function() {
716 notify
&& notify
.close();
717 typeof timmer
!== 'undefined' && window
.clearInterval(timmer
);
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
){
745 angular
.forEach($scope.pusers
, function(id
) {
746 if(id
=== m
.sender_id
){ flg
= true; }
748 if(!flg
) $scope.pusers
.push(m
.sender_id
);
755 $scope.onNewMessage
= function(wasListingForMySubmission
) {
756 if ($scope.lastMessageId
&& !wasListingForMySubmission
) {
758 $scope.pageTitleNotificator
.on('New message');
759 $scope.notifyLastMessage();
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() {
800 pidScroll
= window
.setInterval(function() {
801 $
('.direct-chat-messages').scrollTop(window
.Number
.MAX_SAFE_INTEGER
* 0.001);
802 window
.clearInterval(pidScroll
);
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"});
818 $scope.playAudio
= function() {
819 $scope.beep
&& $scope.beep
.play();
822 $scope.renderMessageBody
= function(html
)
836 border
:1px solid
#6a6a6a;
840 .direct
-chat
-msg
,.direct
-chat
-text
{
842 word
-wrap
: break-word
;
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);
870 height
: calc(100vh
- 175px
);
874 word
-wrap
: break-word
;
876 .direct
-chat
-text
:before
{
880 .direct
-chat
-text
:after
{
884 .direct
-chat
-text
:after
,.direct
-chat
-text
:before
{
888 border
:solid
rgba(0,0,0,0);
889 border
-right
-color
:#D2D6DE;
895 .direct
-chat
-warning
.right
>.direct
-chat
-text
{
896 background
: rgba(251, 255, 178, 0.34);
897 border
-color
: #f30d1b;
900 .right
.direct
-chat
-text
{
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
{
911 border
-right
-color
:rgba(0,0,0,0);
912 border
-left
-color
:#D2D6DE;
914 .right
.direct
-chat
-img
{
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;
924 background
-color
:#FFF;
932 input
,button
,.alert
,.modal
-content
{
933 border
-radius
: 0!important
;
942 background
-color
: ghostwhite
;
947 /*max-height: 730px;*/
948 height
: calc(100vh
- 100px
);
952 background
-color
: ghostwhite
;
956 height
: calc(100vh
- 100px
);
961 padding
: 5px
5px
0 5px
;
964 font
-size
:16px
!important
;
966 label
{display
: block
;}
972 .modal
.modal
-wide
.modal
-dialog
{
975 .modal
-wide
.modal
-body
{
980 <body ng
-controller
="MsgAppCtrl">
981 <div
class="container">
982 <!-- <h2
class="hidden-xs">Secure Chat
</h2
> -->
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
}}
989 <h5
><span
class="label label-default"><?php
echo xlt('Available Recipients'); ?
></span
></h5
>
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
>
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
}}
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
>
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
>
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
>
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
>
1047 <div
class="col-md-2 rtsidebar">
1048 <h5
><span
class="label label-default"><?php
echo xlt('Whose Online'); ?
> : {{ online
.total ||
'0' }}</span
>
1050 <label ng
-repeat
="ol in onlines | unique : 'username'">
1051 <input type
="checkbox" data
-checklist
-model
="onlines" data
-checklist
-value
="ol"> {{ol
.username
}}
1056 <div
class="modal modal-wide fade" id
="popeditor">
1057 <div
class="modal-dialog modal-lg">
1058 <div
class="modal-content">
1060 <div
class="modal-header">
1061 <button type
="button" class="close" data
-dismiss
="modal">
1062 <span aria
-hidden
="true">×
;</span
>
1063 <span
class="sr-only"><?php
echo xlt('Close'); ?
></span
>
1065 <h4
class="modal-title"><?php
echo xlt('Style your messsage and/or add Image/Video'); ?
></h4
>
1067 <div
class="modal-body">
1068 <summernote config
="options"></summernote
>
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
>
1078 <div
class="modal" id
="clear-history">
1079 <div
class="modal-dialog">
1080 <div
class="modal-content">
1082 <div
class="modal-header">
1083 <button type
="button" class="close" data
-dismiss
="modal">
1084 <span aria
-hidden
="true">×
;</span
>
1085 <span
class="sr-only"><?php
echo xlt('Close'); ?
></span
>
1087 <h4
class="modal-title"><?php
echo xlt('Chat history'); ?
></h4
>
1089 <div
class="modal-body">
1090 <label
class="radio"><?php
echo xlt('Are you sure to clear chat history?'); ?
></label
>
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
>