Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8811dd6

Browse files
committedJan 12, 2019
#49 invite new users feature
Also, adding a way to render HTML templates to make HTML emails sane
1 parent 6f18071 commit 8811dd6

33 files changed

+821
-118
lines changed
 

‎.htaccess

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
<IfModule mod_rewrite.c>
77
Options -MultiViews
88

9-
RewriteEngine On
10-
RewriteBase /
9+
# Enable the next two lines if your install lives in a subfolder from the domain base directory
10+
#RewriteEngine On
11+
#RewriteBase /
1112

1213
# Comment next line to disable forcing of HTTPS for all pages:
1314
# (it is strongly recommended you *keep* this setting enabled!)
14-
RewriteCond %{HTTPS} off
15-
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
15+
#RewriteCond %{HTTPS} off
16+
#RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
1617

1718
#H/T to anubhava http://stackoverflow.com/a/29874382/324527
1819
DirectoryIndex bootstrap.php index.html

‎api/Controllers/Commish/UserProfileController.php

+22-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Symfony\Component\HttpFoundation\Request;
77
use Symfony\Component\HttpFoundation\Response;
88
use PhpDraft\Domain\Models\UserProfile;
9+
use PhpDraft\Domain\Entities\LoginUser;
910
use PhpDraft\Domain\Models\PhpDraftResponse;
1011

1112
class UserProfileController {
@@ -30,4 +31,24 @@ public function Put(Application $app, Request $request) {
3031

3132
return $app->json($response, $response->responseType());
3233
}
33-
}
34+
35+
public function InviteNewCommissioner(Application $app, Request $request) {
36+
$user = new LoginUser();
37+
38+
$user->enabled = 0;
39+
$user->email = $request->get('email');
40+
$user->name = $request->get('name');
41+
42+
$message = $request->get('message');
43+
44+
$validity = $app['phpdraft.LoginUserValidator']->isInviteNewUserValid($user, $message);
45+
46+
if (!$validity->success) {
47+
return $app->json($validity, $validity->responseType());
48+
}
49+
50+
$response = $app['phpdraft.UsersService']->InviteNewUser($user, $message);
51+
52+
return $app->json($response, $response->responseType());
53+
}
54+
}

‎api/Domain/Models/MailMessage.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
class MailMessage {
88
public function __construct() {
99
$this->to_addresses = array();
10-
//$this->to_addresses = array (
11-
// "user@example.com"ß
12-
//)
10+
$this->cc_addresses = array();
11+
$this->bcc_addresses = array();
1312
$this->is_html = false;
1413
}
1514

1615
public $to_addresses;
16+
public $cc_addresses;
17+
public $bcc_addresses;
1718
public $subject;
1819
public $body;
1920
public $is_html;
20-
}
21+
}

‎api/Domain/Services/EmailService.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,26 @@ public function __construct(Application $app) {
3535
}
3636

3737
$this->mailer->From = MAIL_USER;
38-
$this->mailer->FromName = 'PHPDraft System';
38+
$this->mailer->FromName = 'Hoot Draft';
3939
}
4040

4141
public function SendMail(MailMessage $message) {
4242
foreach ($message->to_addresses as $address => $name) {
4343
$this->mailer->addAddress($address, $name);
4444
}
4545

46+
if (count($message->cc_addresses) > 0) {
47+
foreach($message->cc_addresses as $address => $name) {
48+
$this->mailer->addCC($address, $name);
49+
}
50+
}
51+
52+
if (count($message->bcc_addresses) > 0) {
53+
foreach($message->bcc_addresses as $address => $name) {
54+
$this->mailer->addBCC($address, $name);
55+
}
56+
}
57+
4658
$this->mailer->isHTML($message->is_html);
4759

4860
$this->mailer->Subject = $message->subject;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
namespace PhpDraft\Domain\Services;
3+
4+
class TemplateRenderService {
5+
private $twig;
6+
7+
public function __construct() {
8+
$loader = new \Twig_Loader_Filesystem(__DIR__ . '/../../EmailTemplates');
9+
$this->twig = new \Twig_Environment($loader);
10+
}
11+
12+
public function RenderTemplate($emailTemplateFilename, $parameters) {
13+
return $this->twig->render($emailTemplateFilename, $parameters);
14+
}
15+
}

‎api/Domain/Services/UsersService.php

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
namespace PhpDraft\Domain\Services;
3+
4+
use Silex\Application;
5+
use Symfony\Component\HttpFoundation\Request;
6+
use PhpDraft\Domain\Entities\LoginUser;
7+
use PhpDraft\Domain\Models\PhpDraftResponse;
8+
use PhpDraft\Domain\Models\MailMessage;
9+
10+
class UsersService {
11+
private $app;
12+
13+
public function __construct(Application $app) {
14+
$this->app = $app;
15+
}
16+
17+
public function InviteNewUser(LoginUser $user, $message) {
18+
$response = new PhpDraftResponse();
19+
20+
$user->password = $this->app['phpdraft.SaltService']->GenerateSalt();
21+
$user->salt = $this->app['phpdraft.SaltService']->GenerateSalt();
22+
$user->roles = array('ROLE_COMMISH');
23+
$user->verificationKey = $this->app['phpdraft.SaltService']->GenerateSaltForUrl();
24+
25+
try {
26+
$user = $this->app['phpdraft.LoginUserRepository']->Create($user);
27+
$inviter = $this->app['phpdraft.LoginUserService']->GetCurrentUser();
28+
29+
$emailParameters = array(
30+
'imageBaseUrl' => sprintf("%s/images/email", APP_BASE_URL),
31+
'invitee' => $user->name,
32+
'inviter' => $inviter->name,
33+
'message' => $message,
34+
'setupLink' => $this->GenerateNewInviteLink($user),
35+
);
36+
37+
$mailMessage = new MailMessage();
38+
39+
$mailMessage->to_addresses = array(
40+
$user->email => $user->name,
41+
);
42+
43+
$mailMessage->subject = "$inviter->name invited you to use Hoot Draft!";
44+
$mailMessage->body = $this->app['phpdraft.TemplateRenderService']->RenderTemplate('UserInvite.html', $emailParameters);;
45+
$mailMessage->is_html = true;
46+
47+
$this->app['phpdraft.EmailService']->SendMail($mailMessage);
48+
49+
$response->success = true;
50+
} catch (\Exception $ex) {
51+
$exceptionMessage = $ex->getMessage();
52+
$response->success = false;
53+
$response->errors[] = $exceptionMessage;
54+
}
55+
56+
return $response;
57+
}
58+
59+
private function GenerateNewInviteLink(LoginUser $user) {
60+
$encodedEmail = urlencode($user->email);
61+
$encodedToken = urlencode($user->verificationKey);
62+
63+
return sprintf("%s/inviteSetup/%s/%s", APP_BASE_URL, $encodedEmail, $encodedToken);
64+
}
65+
}

‎api/Domain/Validators/LoginUserValidator.php

+32
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,38 @@ public function isRegistrationUserValid(Request $request) {
5252
return $this->app['phpdraft.ResponseFactory']($valid, $errors);
5353
}
5454

55+
public function isInviteNewUserValid(LoginUser $user, $message) {
56+
$valid = true;
57+
$errors = array();
58+
59+
$emailAddress = $user->email;
60+
$name = $user->name;
61+
62+
if (empty($emailAddress)
63+
|| empty($name)) {
64+
$errors[] = "One or more missing fields.";
65+
$valid = false;
66+
}
67+
68+
$this->validateNameLength($name, $errors, $valid);
69+
70+
$this->validateEmailAddress($emailAddress, $errors, $valid);
71+
72+
if (strlen($message) > 255) {
73+
$errors[] = "Message too long";
74+
$valid = false;
75+
}
76+
77+
if (!$this->app['phpdraft.LoginUserRepository']->NameIsUnique($name)) {
78+
$errors[] = "Name already taken.";
79+
$valid = false;
80+
}
81+
82+
$this->validateUniqueEmail($emailAddress, $errors, $valid);
83+
84+
return $this->app['phpdraft.ResponseFactory']($valid, $errors);
85+
}
86+
5587
public function IsVerificationValid(Request $request) {
5688
$valid = true;
5789
$errors = array();

‎api/EmailTemplates/UserInvite.html

+1
Large diffs are not rendered by default.

‎api/config/_registerServices.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,12 @@
5454

5555
$app['phpdraft.DatabaseCacheService'] = function() use ($app) {
5656
return new \PhpDraft\Domain\Services\DatabaseCacheService($app);
57-
};
57+
};
58+
59+
$app['phpdraft.UsersService'] = function() use ($app) {
60+
return new \PhpDraft\Domain\Services\UsersService($app);
61+
};
62+
63+
$app['phpdraft.TemplateRenderService'] = function() {
64+
return new \PhpDraft\Domain\Services\TemplateRenderService();
65+
};

‎api/config/_router.php

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
$app->delete('/admin/user/{user_id}', "admin.users.controller:Delete");
131131

132132
$app->get('/commish', "commish.index.controller:Index");
133+
$app->post('/commish/user/invite', "commish.profile.controller:InviteNewCommissioner");
133134
$app->get('/commish/profile', "commish.profile.controller:Get");
134135
$app->put('/commish/profile', "commish.profile.controller:Put");
135136

‎app/features/authentication/authentication.module.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
angular.module('phpdraft.authentication', [
22
'ngRoute',
3-
'ngCapsLock',
43
'toggle-switch',
54
'ui.bootstrap',
65
'validation.match',

‎app/features/authentication/authentication.routes.js

+4
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@ angular.module('phpdraft.authentication').config(($routeProvider, $locationProvi
2626
$routeProvider.when('/profile', {
2727
template: '<phpd-edit-profile></phpd-edit-profile>',
2828
});
29+
30+
$routeProvider.when('/inviteSetup/:email?/:token?', {
31+
template: '<phpd-invite-setup></phpd-invite-setup>',
32+
});
2933
});

‎app/features/authentication/editProfile.component.html

+6-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<div data-ng-cloak>
2-
<div class="container-fluid">
2+
<div class="container-fluid" ng-show="$ctrl.loading">
3+
<phpd-section-loading show-loading="$ctrl.loading" />
4+
</div>
5+
<div class="container-fluid ng-hide" ng-show="!$ctrl.loading">
36
<div class="row">
47
<div class="hidden-xs hidden-sm col-md-1">&nbsp;</div>
58
<div class="col-xs-12 col-sm-12 col-md-10">
@@ -75,22 +78,19 @@ <h2 class="text-center">Update User Profile</h2>
7578
<div class="form-group" show-errors>
7679
<div class="col-xs-3 text-right">
7780
<label class="control-label" for="password">
78-
<span class="text-danger">*</span>&nbsp;Password
81+
<span class="text-danger">*</span>&nbsp;Current Password
7982
</label>
8083
</div>
8184
<div class="col-xs-6">
8285
<input name="password"
8386
ng-model="$ctrl.userProfile.password"
8487
autocomplete="current-password"
85-
uib-tooltip="Capslock is on"
86-
tooltip-trigger="focus"
87-
tooltip-enable="isCapsLockOn"
8888
class="form-control"
8989
minlength="8"
9090
maxlength="255"
9191
type="{{$ctrl.passwordInputType()}}"
9292
ng-required="true"
93-
placeholder="Password" />
93+
placeholder="Current password (for security, you know)" />
9494
<div ng-show="$ctrl.form.$submitted || $ctrl.form.password.$touched">
9595
<span ng-show="$ctrl.form.password.$error.required" class="help-block">Password is missing</span>
9696
<span ng-show="!$ctrl.form.password.$error.required && $ctrl.form.password.$error.minlength" class="help-block">Password is too short - 8 characters minimum</span>
@@ -116,9 +116,6 @@ <h2 class="text-center">Update User Profile</h2>
116116
name="newPassword"
117117
ng-model="$ctrl.userProfile.newPassword"
118118
autocomplete="new-password"
119-
uib-tooltip="Capslock is on"
120-
tooltip-trigger="focus"
121-
tooltip-enable="isCapsLockOn"
122119
class="form-control"
123120
minlength="8"
124121
maxlength="255"
@@ -146,10 +143,7 @@ <h2 class="text-center">Update User Profile</h2>
146143
<div class="col-xs-6">
147144
<input id="newConfirmedPassword"
148145
name="newConfirmedPassword"
149-
uib-tooltip="Capslock is on"
150146
autocomplete="new-password"
151-
tooltip-trigger="focus"
152-
tooltip-enable="isCapsLockOn"
153147
ng-model="$ctrl.userProfile.newConfirmedPassword"
154148
class="form-control"
155149
minlength="8"

‎app/features/authentication/editProfile.component.js

+5
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,27 @@ class EditProfileController {
1212
this.errorService = errorService;
1313
this.pathHelperService = pathHelperService;
1414
this.lodash = lodash;
15+
16+
this.loading = true;
1517
}
1618

1719
$onInit() {
1820
this.showPassword = false;
1921
this.userProfile = {};
22+
this.loading = true;
2023

2124
this.loadUserProfileData();
2225
}
2326

2427
loadUserProfileData() {
2528
const loadSuccess = data => {
2629
this.lodash.merge(this.userProfile, data);
30+
this.loading = false;
2731
};
2832

2933
const errorHandler = () => {
3034
this.messageService.showError('Unable to load user profile');
35+
this.loading = false;
3136
};
3237

3338
this.api.Authentication.getProfile({}, loadSuccess, errorHandler);

0 commit comments

Comments
 (0)
Please sign in to comment.