diff --git a/.idea/deployment.xml b/.idea/deployment.xml new file mode 100644 index 0000000..0f6bfd7 --- /dev/null +++ b/.idea/deployment.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api/api.php b/api/api.php index f46c75b..a728379 100644 --- a/api/api.php +++ b/api/api.php @@ -3,6 +3,7 @@ require_once('lib/Slim/Slim.php'); require_once('lib/rb.php'); require_once('lib/password.php'); require_once('lib/JWT.php'); +require_once('lib/PHPMailer/PHPMailerAutoload.php'); require_once('jsonResponse.php'); @@ -51,6 +52,8 @@ $app->get('/authenticate', function() use($app, $jsonResponse) { $app->response->setBody($jsonResponse->asJson()); }); +require_once('mailFactory.php'); + require_once('userRoutes.php'); require_once('boardRoutes.php'); require_once('itemRoutes.php'); diff --git a/api/boardRoutes.php b/api/boardRoutes.php index 35fea6f..63f2179 100644 --- a/api/boardRoutes.php +++ b/api/boardRoutes.php @@ -19,6 +19,15 @@ $app->post('/boards', function() use($app, $jsonResponse) { logAction($actor->username . ' added board ' . $board->name, null, $board->export()); $jsonResponse->addBeans(getBoards()); $jsonResponse->addAlert('success', 'New board ' . $board->name . ' created.'); + + foreach($board->sharedUser as $user) { + $body = getNewBoardEmailBody($board->id, $user->username, $board->name); + $subject = 'New board created!'; + $recipient = $user->username; + $email = $user->email; + + sendEmail($email, $recipient, $subject, $body); + } } $app->response->setBody($jsonResponse->asJson()); }); @@ -37,6 +46,15 @@ $app->post('/boards/update', function() use($app, $jsonResponse) { logAction($actor->username . ' updated board ' . $board->name, $before, $board->export()); } $jsonResponse->addBeans(getBoards()); + + foreach($board->sharedUser as $user) { + $body = geEditBoardEmailBody($board->id, $user->username, $board->name); + $subject = 'Board updated!'; + $recipient = $user->username; + $email = $user->email; + + sendEmail($email, $recipient, $subject, $body); + } } $app->response->setBody($jsonResponse->asJson()); }); diff --git a/api/helpers.php b/api/helpers.php index 473d11c..d332420 100644 --- a/api/helpers.php +++ b/api/helpers.php @@ -66,6 +66,30 @@ function getUser() { return null; } +function getUserByID($id) { + try { + $user = R::load('user', $id); + + if ($user->id) { + return $user; + } + } catch (Exception $e) {} + + return null; +} + +function getLaneByID($id) { + try { + $lane = R::load('lane', $id); + + if ($lane->id) { + return $lane; + } + } catch (Exception $e) {} + + return null; +} + // Get all users. function getUsers($sanitize = true) { try { @@ -240,6 +264,21 @@ function updateUsername($user, $data) { return $user; } +// Change email if available. +function updateEmail($user, $data) { + global $jsonResponse; + $emailTaken = R::findOne('user', ' username = ?', [$data->newEmail]); + + if (null != $user && null == $emailTaken) { + $user->email = $data->newEmail; + $jsonResponse->addAlert('success', 'Email updated.'); + } else { + $jsonResponse->addAlert('error', 'Email already in use.'); + } + + return $user; +} + // Validate a provided JWT. function validateToken($requireAdmin = false) { global $jsonResponse, $app; @@ -333,6 +372,7 @@ function createInitialUser() { $admin->defaultBoard = null; $admin->salt = password_hash($admin->username . time(), PASSWORD_BCRYPT); $admin->password = password_hash('admin', PASSWORD_BCRYPT, array('salt' => $admin->salt)); + $admin->email = ''; R::store($admin); } diff --git a/api/itemRoutes.php b/api/itemRoutes.php index 4ca2b75..4baa3b9 100644 --- a/api/itemRoutes.php +++ b/api/itemRoutes.php @@ -29,6 +29,27 @@ $app->post('/boards/:id/items', function($id) use($app, $jsonResponse) { } else { $jsonResponse->addAlert('error', 'Failed to create board item.'); } + + foreach($board->sharedUser as $user) { + $actor = getUser(); + $body = getNewItemEmailBody( + $board->id, + $actor->username, + $board->name, + $item->title, + $item->description, + getUserByID($item->assignee)->username, + $item->category, + $item->dueDate, + $item->points, + $item->position + ); + $subject = 'New item created!'; + $recipient = $user->username; + $email = $user->email; + + sendEmail($email, $recipient, $subject, $body); + } } } $app->response->setBody($jsonResponse->asJson()); @@ -62,6 +83,30 @@ $app->post('/items/:itemId', function($itemId) use ($app, $jsonResponse) { logAction($user->username . ' updated item ' . $item->title, $before, $item->export(), $itemId); $jsonResponse->addAlert('success', 'Updated item ' . $item->title . '.'); $jsonResponse->addBeans(getBoards()); + + $lane = R::load('lane', $item->lane_id); + $board = R::load('board', $lane->boardId); + + foreach($board->sharedUser as $user) { + $actor = getUser(); + $body = getEditItemEmailBody( + $board->id, + $actor->username, + $board->name, + $item->title, + $item->description, + getUserByID($item->assignee)->username, + $item->category, + $item->dueDate, + $item->points, + $item->position + ); + $subject = 'Item edited'; + $recipient = $user->username; + $email = $user->email; + + sendEmail($email, $recipient, $subject, $body); + } } } $app->response->setBody($jsonResponse->asJson()); @@ -123,6 +168,24 @@ $app->post('/items/:itemId/comment', function($itemId) use ($app, $jsonResponse) logAction($user->username . ' added a comment to item ' . $item->title, null, $comment->export(), $itemId); $jsonResponse->addAlert('success', 'Comment added to item ' . $item->title . '.'); $jsonResponse->addBeans(R::load('item', $itemId)); + + $lane = R::load('lane', $item->lane_id); + $board = R::load('board', $lane->boardId); + + foreach($board->sharedUser as $user) { + $body = getNewCommentEmailBody( + $board->id, + $user->username, + $board->name, + $item->title, + $comment->text + ); + $subject = 'New comment'; + $recipient = $user->username; + $email = $user->email; + + sendEmail($email, $recipient, $subject, $body); + } } } $app->response->setBody($jsonResponse->asJson()); @@ -146,6 +209,25 @@ $app->post('/comments/:commentId', function($commentId) use ($app, $jsonResponse logAction($user->username . ' edited comment ' . $comment->id, $before, $comment->export(), $comment->id); $jsonResponse->addAlert('success', 'Comment edited.'); $jsonResponse->addBeans(R::load('item', $comment->item_id)); + + $item = R::load('item', $comment->item_id); + $lane = R::load('lane', $item->lane_id); + $board = R::load('board', $lane->boardId); + + foreach($board->sharedUser as $user) { + $body = getEditCommentEmailBody( + $board->id, + $user->username, + $board->name, + $item->title, + $comment->text + ); + $subject = 'Edit comment'; + $recipient = $user->username; + $email = $user->email; + + sendEmail($email, $recipient, $subject, $body); + } } $app->response->setBody($jsonResponse->asJson()); })->conditions(['commentId' => '\d+']); diff --git a/api/mailConfig.php b/api/mailConfig.php new file mode 100644 index 0000000..2b13ece --- /dev/null +++ b/api/mailConfig.php @@ -0,0 +1,9 @@ +isSMTP(); + $mail->Host = $MAIL_HOST; + $mail->SMTPAuth = true; + $mail->Username = $MAIL_USERNAME; + $mail->Password = $MAIL_PASSWORD; + $mail->SMTPSecure = $MAIL_SMTPSECURE; + $mail->Port = $MAIL_PORT; + + $mail->From = $MAIL_FROM; + $mail->FromName = $MAIL_FROMNAME; + + return $mail; +} + +function sendEmail($email, $recipient, $subject, $body) { + $mail = createMailObject(); + $mail->addAddress($email, $recipient); // Add a recipient + $mail->isHTML(true); + + $mail->Subject = $subject; + $mail->Body = $body; + + $mail->send(); +} + +function getNewBoardEmailBody($boardid, $username, $boardname) { + $message = file_get_contents('mail_templates/newBoard.html'); + $message = str_replace('%boardid%', $boardid, $message); + $message = str_replace('%username%', $username, $message); + $message = str_replace('%boardname%', $boardname, $message); + return $message; +} + +function getEditBoardEmailBody($boardid, $username, $boardname) { + $message = file_get_contents('mail_templates/editBoard.html'); + $message = str_replace('%boardid%', $boardid, $message); + $message = str_replace('%username%', $username, $message); + $message = str_replace('%boardname%', $boardname, $message); + return $message; +} + +function getNewItemEmailBody($boardid, $username, $boardname, $title, $description, $assignee, $category, $dueDate, $points, $position) +{ + $message = file_get_contents('mail_templates/newItem.html'); + $message = str_replace('%boardid%', $boardid, $message); + $message = str_replace('%username%', $username, $message); + $message = str_replace('%boardname%', $boardname, $message); + $message = str_replace('%title%', $title, $message); + $message = str_replace('%description%', $description, $message); + $message = str_replace('%assignee%', $assignee, $message); + $message = str_replace('%category%', $category, $message); + $message = str_replace('%duedate%', $dueDate, $message); + $message = str_replace('%points%', $points, $message); + $message = str_replace('%position%', $position, $message); + return $message; +} + +function getEditItemEmailBody($boardid, $username, $boardname, $title, $description, $assignee, $category, $dueDate, $points, $position) +{ + $message = file_get_contents('mail_templates/editItem.html'); + $message = str_replace('%boardid%', $boardid, $message); + $message = str_replace('%username%', $username, $message); + $message = str_replace('%boardname%', $boardname, $message); + $message = str_replace('%title%', $title, $message); + $message = str_replace('%description%', $description, $message); + $message = str_replace('%assignee%', $assignee, $message); + $message = str_replace('%category%', $category, $message); + $message = str_replace('%duedate%', $dueDate, $message); + $message = str_replace('%points%', $points, $message); + $message = str_replace('%position%', $position, $message); + return $message; +} + +function getNewCommentEmailBody($boardid, $username, $boardname, $title, $comment) +{ + $message = file_get_contents('mail_templates/newComment.html'); + $message = str_replace('%boardid%', $boardid, $message); + $message = str_replace('%username%', $username, $message); + $message = str_replace('%boardname%', $boardname, $message); + $message = str_replace('%title%', $title, $message); + $message = str_replace('%comment%', $comment, $message); + return $message; +} + +function getEditCommentEmailBody($boardid, $username, $boardname, $title, $comment) +{ + $message = file_get_contents('mail_templates/editComment.html'); + $message = str_replace('%boardid%', $boardid, $message); + $message = str_replace('%username%', $username, $message); + $message = str_replace('%boardname%', $boardname, $message); + $message = str_replace('%title%', $title, $message); + $message = str_replace('%comment%', $comment, $message); + return $message; +} \ No newline at end of file diff --git a/api/mail_templates/editBoard.html b/api/mail_templates/editBoard.html new file mode 100644 index 0000000..4c476cb --- /dev/null +++ b/api/mail_templates/editBoard.html @@ -0,0 +1,7 @@ + + + +%username% updated board %boardname%! +Navigate to board! + + \ No newline at end of file diff --git a/api/mail_templates/editComment.html b/api/mail_templates/editComment.html new file mode 100644 index 0000000..787ef01 --- /dev/null +++ b/api/mail_templates/editComment.html @@ -0,0 +1,10 @@ + + + +%username% edited comment at board %boardname%:
+%title%
+%comment%
+
+Navigate to board! + + \ No newline at end of file diff --git a/api/mail_templates/editItem.html b/api/mail_templates/editItem.html new file mode 100644 index 0000000..159e8a3 --- /dev/null +++ b/api/mail_templates/editItem.html @@ -0,0 +1,15 @@ + + + +%username% edited item at board %boardname%:
+%title%
+%description%
+%duedate%
+%assignee%
+%category%
+%points%
+%position%
+Navigate to board! + + + \ No newline at end of file diff --git a/api/mail_templates/newBoard.html b/api/mail_templates/newBoard.html new file mode 100644 index 0000000..f593e8b --- /dev/null +++ b/api/mail_templates/newBoard.html @@ -0,0 +1,7 @@ + + + +%username% created new board %boardname%!
+Navigate to board! + + \ No newline at end of file diff --git a/api/mail_templates/newComment.html b/api/mail_templates/newComment.html new file mode 100644 index 0000000..41561df --- /dev/null +++ b/api/mail_templates/newComment.html @@ -0,0 +1,10 @@ + + + +%username% created new comment at board %boardname%:
+%title%
+%comment%
+
+Navigate to board! + + \ No newline at end of file diff --git a/api/mail_templates/newItem.html b/api/mail_templates/newItem.html new file mode 100644 index 0000000..78e047f --- /dev/null +++ b/api/mail_templates/newItem.html @@ -0,0 +1,15 @@ + + + +%username% created new item at board %boardname%:
+%title%
+%description%
+%duedate%
+%assignee%
+%category%
+%points%
+%position%
+Navigate to board! + + + \ No newline at end of file diff --git a/api/userRoutes.php b/api/userRoutes.php index b0fcc7d..811cdfd 100644 --- a/api/userRoutes.php +++ b/api/userRoutes.php @@ -86,6 +86,24 @@ $app->post('/updateusername', function() use($app, $jsonResponse) { $app->response->setBody($jsonResponse->asJson()); }); +// Update current user's email if not taken. +$app->post('/updateemail', function() use($app, $jsonResponse) { + $data = json_decode($app->environment['slim.input']); + + if (validateToken()) { + $user = getUser(); + $before = $user->export(); + $user = updateEmail($user, $data); + R::store($user); + + if ($jsonResponse->alerts[0]['type'] == 'success') { + logAction($before['username'] . ' changed email to ' . $user->email, $before, $user->export()); + } + $jsonResponse->addBeans(getUsers()); + } + $app->response->setBody($jsonResponse->asJson()); +}); + // Update current user's default board. $app->post('/updateboard', function() use($app, $jsonResponse) { $data = json_decode($app->environment['slim.input']); @@ -123,6 +141,7 @@ $app->get('/users/current', function() use($app, $jsonResponse) { 'userId' => $user->id, 'username' => $user->username, 'isAdmin' => $user->isAdmin, + 'email' => $user->email, 'defaultBoard' => $user->defaultBoard ]; } @@ -151,6 +170,7 @@ $app->post('/users', function() use($app, $jsonResponse) { $user = R::dispense('user'); $user->username = $data->username; $user->isAdmin = $data->isAdmin; + $user->email = $data->email; $user->defaultBoard = $data->defaultBoard; $user->salt = password_hash($data->username . time(), PASSWORD_BCRYPT); $user->password = password_hash($data->password, PASSWORD_BCRYPT, array('salt' => $user->salt)); @@ -187,6 +207,7 @@ $app->post('/users/update', function() use($app, $jsonResponse) { $user->password = password_hash($data->password, PASSWORD_BCRYPT, array('salt' => $user->salt)); } $user->isAdmin = $data->isAdmin; + $user->email = $data->email; $user->defaultBoard = $data->defaultBoard; R::store($user); diff --git a/js/controllers/settingsUser.js b/js/controllers/settingsUser.js index 88303d1..b22b9e5 100644 --- a/js/controllers/settingsUser.js +++ b/js/controllers/settingsUser.js @@ -117,6 +117,40 @@ function ($scope, $interval, UserService) { } }; + $scope.emailFormData = { + newEmail: '', + emailError: false, + isSaving: false, + setAlert: function(message) { + this.isSaving = false; + this.emailError = true; + $scope.alerts.showAlert({ 'type': 'error', 'text': message }); + }, + reset: function() { + this.newEmail = ''; + this.emailError = false; + this.isSaving = false; + } + }; + $scope.changeEmail = function(newEmailFormData) { + $scope.emailFormData.isSaving = true; + + if (newEmailFormData.newEmail === '') { + newEmailFormData.setAlert('Email cannot be blank.'); + newEmailFormData.isSaving = false; + } else { + UserService.changeEmail(newEmailFormData.newEmail) + .success(function(data) { + $scope.alerts.showAlerts(data.alerts); + $scope.updateUsers(data.data); + $scope.loadCurrentUser(); + + newEmailFormData.isSaving = false; + newEmailFormData.newUsername = ''; + }); + } + }; + $scope.updatingDefaultBoard = false; $scope.setDefaultBoard = function() { $scope.updatingDefaultBoard = true; diff --git a/js/controllers/settingsUserForm.js b/js/controllers/settingsUserForm.js index 49090a2..7599b2e 100644 --- a/js/controllers/settingsUserForm.js +++ b/js/controllers/settingsUserForm.js @@ -7,11 +7,13 @@ function ($scope, UserService) { isAdd: true, username: '', password: '', + email: '', verifyPass: '', defaultBoard: null, isAdmin: false, passError: false, usernameError: false, + emailError: false, isSaving: false, setUser: function(user) { this.reset(); @@ -19,6 +21,7 @@ function ($scope, UserService) { this.isAdd = false; this.userId = user.id; this.username = user.username; + this.email = user.email; this.defaultBoard = user.default_board; this.isAdmin = user.is_admin == '1'; }, @@ -29,6 +32,7 @@ function ($scope, UserService) { this.isAdd = true; this.username = ''; this.password = ''; + this.email = ''; this.verifyPass = ''; this.defaultBoard = null; this.isAdmin = false; diff --git a/js/services/user.js b/js/services/user.js index a97cad9..891b47f 100644 --- a/js/services/user.js +++ b/js/services/user.js @@ -35,6 +35,12 @@ function($http) { }); }, + changeEmail: function(newEmail) { + return $http.post('api/updateemail', { + newEmail: newEmail + }); + }, + changeDefaultBoard: function(newDefaultBoard) { return $http.post('api/updateboard', { defaultBoard: newDefaultBoard @@ -53,6 +59,7 @@ function($http) { return $http.post('api/users', { username: formData.username, password: formData.password, + email: formData.email, defaultBoard: formData.defaultBoard, boardAccess: formData.boardAccess, isAdmin: formData.isAdmin @@ -64,6 +71,7 @@ function($http) { userId: formData.userId, newUsername: formData.username, password: formData.password, + email: formData.email, defaultBoard: formData.defaultBoard, boardAccess: formData.boardAccess, isAdmin: formData.isAdmin diff --git a/partials/settingsModifyUser.html b/partials/settingsModifyUser.html index dd438f2..cc15301 100644 --- a/partials/settingsModifyUser.html +++ b/partials/settingsModifyUser.html @@ -41,4 +41,16 @@ +
+

Change Email

+
+
+
+ +
+ + Reset +
+
+
diff --git a/partials/settingsUserModal.html b/partials/settingsUserModal.html index 8aaeba1..ffd5a0a 100644 --- a/partials/settingsUserModal.html +++ b/partials/settingsUserModal.html @@ -23,6 +23,10 @@
+
+
Change Email
+ +
Select Default Board - UserAdminDefault BoardActions + UserEmailAdminDefault BoardActions @@ -15,6 +15,7 @@ {{ user.username }} + {{ user.email }}