diff --git a/app/database/init.php b/app/database/init.php index 0f34725..7935cc7 100644 --- a/app/database/init.php +++ b/app/database/init.php @@ -31,6 +31,7 @@ $stmt = 'CREATE TABLE IF NOT EXISTS messages ( enc_keys VARCHAR(64) NOT NULL, keys_iv VARCHAR(24) NOT NULL, message_iv VARCHAR(24) NOT NULL, + is_read BOOLEAN NOT NULL DEFAULT 0, FOREIGN KEY (sender_id) REFERENCES users(id), FOREIGN KEY (receiver_id) REFERENCES users(id) )'; diff --git a/app/model/SecureMessage.php b/app/model/SecureMessage.php index f604447..849e61b 100644 --- a/app/model/SecureMessage.php +++ b/app/model/SecureMessage.php @@ -11,11 +11,21 @@ class SecureMessage { public $message; public $sender_id; public $receiver_id; + public $is_read = false; /** * Stores this information pertaining to this message in the DB */ public function save() { + $dbh = Database::connect(); + if (SecureMessage::exists($this->id)) { + $stmt = $dbh->prepare("UPDATE messages SET is_read=:is_read WHERE id=:message_id"); + $is_read = (int) $this->is_read; + $stmt->bindParam(':is_read', $is_read); + $stmt->bindParam(':message_id', $this->id); + $stmt->execute(); + return; + } $integrity_key = openssl_random_pseudo_bytes(16); $encryption_key = openssl_random_pseudo_bytes(16); $message_iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(Config::encr_algo)); @@ -27,7 +37,6 @@ class SecureMessage { $enc_keys = openssl_encrypt($integrity_key . $encryption_key, Config::encr_algo, Config::secret_key, 0, $keys_iv); - $dbh = Database::connect(); $stmt = $dbh->prepare('INSERT INTO messages (message, sender_id, receiver_id, enc_keys, keys_iv, message_iv) VALUES (:message, :sender_id, :receiver_id, :enc_keys, :keys_iv, :message_iv) '); @@ -52,6 +61,24 @@ class SecureMessage { $stmt->execute(); } + public static function exists($message_id) { + $dbh = Database::connect(); + $stmt = $dbh->prepare("SELECT count(*) FROM messages WHERE id = :message_id"); + $stmt->bindParam(':message_id', $message_id); + $stmt->execute(); + return $stmt->fetchColumn() > 0; + } + + public static function getByID($message_id) { + $messages = SecureMessage::all(); + foreach ($messages as $m) { + if ($m->id == $message_id) { + return $m; + } + } + return null; + } + public static function all() { $dbh = Database::connect(); $stmt = $dbh->prepare("SELECT * from messages"); @@ -81,6 +108,7 @@ class SecureMessage { $m->message = $message; $m->sender_id = $row['sender_id']; $m->receiver_id = $row['receiver_id']; + $m->is_read = (bool) $row['is_read']; return $m; }, $stmt->fetchAll()); @@ -111,6 +139,13 @@ class SecureMessage { }); } + public static function count_unread($user_id) { + $messages = SecureMessage::all(); + return count(array_filter($messages, function ($m) use ($user_id) { + return $m->receiver_id == $user_id && ! $m->is_read; + })); + } + public function sender() { return User::getByID($this->sender_id); } diff --git a/public/api.php b/public/api.php new file mode 100644 index 0000000..454fc0f --- /dev/null +++ b/public/api.php @@ -0,0 +1,39 @@ +is_read = true; + $m->save(); + echo json_encode(['success']); + } else if ($action === 'send_message') { + $user = User::getByID($params['user_id']); + $recipient = User::get($params['recipient']); + if (!$recipient) { + echo json_encode(['unknown_recipient']); + } else { + $m = new SecureMessage; + $m->message = $params['message']; + $m->sender_id = $user->id; + $m->receiver_id = $recipient->id; + $m->save(); + echo json_encode(['success']); + } + } + +} +?> diff --git a/public/css/app.css b/public/css/app.css index 6bc964c..0ba24a8 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -1,7 +1,8 @@ @import url("login.css"); @import url("admin.css"); @import url("user.css"); - +@import url("menu-button.css"); +@import url("messages.css"); body { background-color: #fcfcfc !important; @@ -15,3 +16,10 @@ main { input:-webkit-autofill { -webkit-box-shadow: 0 0 0 1000px white inset !important; } + +.notification-count { + background-color: #f44336; + border-radius: 50%; + padding: 2px 6px; + margin-left: 5px; +} diff --git a/public/css/menu-button.css b/public/css/menu-button.css new file mode 100644 index 0000000..3e0d083 --- /dev/null +++ b/public/css/menu-button.css @@ -0,0 +1,3 @@ +.menu-messages-count { + min-width: 1rem !important; +} diff --git a/public/css/messages.css b/public/css/messages.css new file mode 100644 index 0000000..e92a689 --- /dev/null +++ b/public/css/messages.css @@ -0,0 +1,15 @@ +.messages-container { + padding-top: 50px; +} + +.send-message-container { + position: absolute; + width: 100%; + left: 0; + bottom: 10px; +} + +.message-compose-box { + padding: 10px !important; + border-radius: 5px; +} diff --git a/public/js/app.js b/public/js/app.js index 50f51c0..c9c5d17 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -6,8 +6,8 @@ $(() => $('#login-form').addClass('scale-in')); $(() => $('.user-tile').addClass('scale-in')); $(document).ready(function(){ - // the "href" attribute of .modal-trigger must specify the modal ID that wants to be triggered $('.modal').modal(); + Materialize.showStaggeredList('#messages-list') }); $('.dropdown-button').dropdown({ @@ -25,3 +25,59 @@ $('.dropdown-button').dropdown({ $(document).ready(function(){ $('.collapsible').collapsible(); }); + + $('#messages-list li').click(function() { + var message_id = this.value; + var message_icon = $('#inbox-icon-' + message_id); + if (message_icon.html().trim() != 'check') { + var params = new URLSearchParams(); + params.append('action', 'mark_unread'); + params.append('message_id', message_id); + axios.post('/api.php', params).then(function(response) { + message_icon.html('check'); + message_icon.removeClass('red-text'); + }); + } + }) + + function addToSent(recipient, message) { + sentList = $('#sent-list'); + var listItem = '