First working version of invite users feature
This commit is contained in:
parent
aa7cefc959
commit
5331c3363e
|
@ -4,6 +4,7 @@ $userControllers->setGroupPath('/user');
|
|||
|
||||
$userControllers->addController(new LoginController);
|
||||
$userControllers->addController(new SignUpController);
|
||||
$userControllers->addController(new InviteUserController);
|
||||
$userControllers->addController(new LogoutController);
|
||||
$userControllers->addController(new CheckSessionController);
|
||||
$userControllers->addController(new SendRecoverPasswordController);
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
use Respect\Validation\Validator as DataValidator;
|
||||
DataValidator::with('CustomValidations', true);
|
||||
|
||||
/**
|
||||
* @api {post} /user/invite Invite
|
||||
* @apiVersion 4.5.0
|
||||
*
|
||||
* @apiName Invite
|
||||
*
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiDescription This path invites an user on the system.
|
||||
*
|
||||
* @apiPermission staff1
|
||||
*
|
||||
* @apiParam {String} name The name of the invited user.
|
||||
* @apiParam {String} email The email of the invited user.
|
||||
* @apiParam {String} customfield_ Custom field values for this user.
|
||||
*
|
||||
* @apiUse INVALID_NAME
|
||||
* @apiUse INVALID_EMAIL
|
||||
* @apiUse INVALID_CAPTCHA
|
||||
* @apiUse USER_EXISTS
|
||||
* @apiUse ALREADY_BANNED
|
||||
* @apiUse NO_PERMISSION
|
||||
* @apiUse INVALID_CUSTOM_FIELD_OPTION
|
||||
* @apiUse MAIL_SENDER_NOT_CONNECTED
|
||||
*
|
||||
* @apiSuccess {Object} data Information about invited user
|
||||
* @apiSuccess {Number} data.userId Id of the invited user
|
||||
* @apiSuccess {String} data.userEmail Email of the invited user
|
||||
*
|
||||
*/
|
||||
|
||||
class InviteUserController extends Controller {
|
||||
const PATH = '/invite';
|
||||
const METHOD = 'POST';
|
||||
|
||||
private $userEmail;
|
||||
private $userName;
|
||||
|
||||
public function validations() {
|
||||
$validations = [
|
||||
'permission' => 'staff_1',
|
||||
'requestData' => [
|
||||
'name' => [
|
||||
'validation' => DataValidator::length(2, 55),
|
||||
'error' => ERRORS::INVALID_NAME
|
||||
],
|
||||
'email' => [
|
||||
'validation' => DataValidator::email(),
|
||||
'error' => ERRORS::INVALID_EMAIL
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$validations['requestData']['captcha'] = [
|
||||
'validation' => DataValidator::captcha(),
|
||||
'error' => ERRORS::INVALID_CAPTCHA
|
||||
];
|
||||
|
||||
return $validations;
|
||||
}
|
||||
|
||||
public function handler() {
|
||||
if (!Controller::isUserSystemEnabled()) {
|
||||
throw new RequestException(ERRORS::USER_SYSTEM_DISABLED);
|
||||
}
|
||||
|
||||
if (!Setting::getSetting('registration')->value) {
|
||||
throw new RequestException(ERRORS::NO_PERMISSION);
|
||||
}
|
||||
|
||||
$this->storeRequestData();
|
||||
|
||||
$existentUser = User::getUser($this->userEmail, 'email');
|
||||
|
||||
if (!$existentUser->isNull()) {
|
||||
throw new RequestException(ERRORS::USER_EXISTS);
|
||||
}
|
||||
|
||||
$banRow = Ban::getDataStore($this->userEmail, 'email');
|
||||
|
||||
if (!$banRow->isNull()) {
|
||||
throw new RequestException(ERRORS::ALREADY_BANNED);
|
||||
}
|
||||
|
||||
if(MailSender::getInstance()->isConnected()) {
|
||||
$userId = $this->createNewUserAndRetrieveId();
|
||||
|
||||
$this->token = Hashing::generateRandomToken();
|
||||
|
||||
$recoverPassword = new RecoverPassword();
|
||||
$recoverPassword->setProperties(array(
|
||||
'email' => $this->userEmail,
|
||||
'token' => $this->token,
|
||||
'staff' => false
|
||||
));
|
||||
$recoverPassword->store();
|
||||
|
||||
$this->sendInvitationMail();
|
||||
|
||||
Response::respondSuccess([
|
||||
'userId' => $userId,
|
||||
'userEmail' => $this->userEmail
|
||||
]);
|
||||
// TODO: Log::createLog('SIGN_UP', null, User::getDataStore($userId));
|
||||
} else {
|
||||
throw new RequestException(ERRORS::MAIL_SENDER_NOT_CONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
public function storeRequestData() {
|
||||
$this->userName = Controller::request('name');
|
||||
$this->userEmail = Controller::request('email');
|
||||
}
|
||||
|
||||
public function createNewUserAndRetrieveId() {
|
||||
$userInstance = new User();
|
||||
|
||||
$userInstance->setProperties([
|
||||
'name' => $this->userName,
|
||||
'signupDate' => Date::getCurrentDate(),
|
||||
'tickets' => 0,
|
||||
'email' => $this->userEmail,
|
||||
'password' => Hashing::hashPassword(Hashing::generateRandomToken()),
|
||||
'verificationToken' => null,
|
||||
'xownCustomfieldvalueList' => $this->getCustomFieldValues()
|
||||
]);
|
||||
|
||||
return $userInstance->store();
|
||||
}
|
||||
|
||||
public function sendInvitationMail() {
|
||||
$mailSender = MailSender::getInstance();
|
||||
|
||||
$mailSender->setTemplate(MailTemplate::USER_INVITE, [
|
||||
'to' => $this->userEmail,
|
||||
'name' => $this->userName,
|
||||
'url' => Setting::getSetting('url')->getValue(),
|
||||
'token' => $this->token
|
||||
]);
|
||||
|
||||
$mailSender->send();
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ DataValidator::with('CustomValidations', true);
|
|||
* @apiParam {String} name The name of the new user.
|
||||
* @apiParam {String} email The email of the new user.
|
||||
* @apiParam {String} password The password of the new user.
|
||||
* @apiParam {String} apiKey APIKey to sign up an user if the user system is disabled.
|
||||
* @apiParam {String} apiKey APIKey to sign up an user if the registration system is disabled.
|
||||
* @apiParam {String} customfield_ Custom field values for this user.
|
||||
*
|
||||
* @apiUse INVALID_NAME
|
||||
|
|
|
@ -251,6 +251,10 @@
|
|||
* @apiDefine INVALID_COLOR
|
||||
* @apiError {String} INVALID_COLOR The color should be in hexadecimal, preceded by a '#'
|
||||
*/
|
||||
/**
|
||||
* @apiDefine MAIL_SENDER_NOT_CONNECTED
|
||||
* @apiError {String} MAIL_SENDER_NOT_CONNECTED The mail sender is not connected.
|
||||
*/
|
||||
|
||||
class ERRORS {
|
||||
const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS';
|
||||
|
@ -317,4 +321,5 @@ class ERRORS {
|
|||
const INVALID_CUSTOM_FIELD_OPTION = 'INVALID_CUSTOM_FIELD_OPTION';
|
||||
const UNAVAILABLE_STATS = 'UNAVAILABLE_STATS';
|
||||
const INVALID_COLOR = 'INVALID_COLOR';
|
||||
const MAIL_SENDER_NOT_CONNECTED = 'MAIL_SENDER_NOT_CONNECTED';
|
||||
}
|
||||
|
|
|
@ -25,6 +25,12 @@ class MailTexts {
|
|||
'Hi, {{name}}. You have requested to recover your password.',
|
||||
'Use this code in {{url}}/recover-password?email={{to}}&token={{token}} or click the button below.',
|
||||
],
|
||||
'USER_INVITE' => [
|
||||
'User invited - OpenSupports',
|
||||
'User invited',
|
||||
'Hi, {{name}}. You have been invited to join our support center.',
|
||||
'Use this code in {{url}}/recover-password?email={{to}}&token={{token}}&invited=true or click the button below to set up your password.'
|
||||
],
|
||||
'USER_SYSTEM_DISABLED' => [
|
||||
'Access system changed - OpenSupports',
|
||||
'Access system changed',
|
||||
|
|
|
@ -0,0 +1,384 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Support Center</title>
|
||||
|
||||
<style type="text/css">
|
||||
/* Take care of image borders and formatting, client hacks */
|
||||
img { max-width: 600px; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic;}
|
||||
a img { border: none; }
|
||||
table { border-collapse: collapse !important;}
|
||||
#outlook a { padding:0; }
|
||||
.ReadMsgBody { width: 100%; }
|
||||
.ExternalClass { width: 100%; }
|
||||
.backgroundTable { margin: 0 auto; padding: 0; width: 100% !important; }
|
||||
table td { border-collapse: collapse; }
|
||||
.ExternalClass * { line-height: 115%; }
|
||||
.container-for-gmail-android { min-width: 600px; }
|
||||
|
||||
|
||||
/* General styling */
|
||||
* {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: none;
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
height: 100%;
|
||||
color: #676767;
|
||||
}
|
||||
|
||||
td {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #777777;
|
||||
text-align: center;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #676767;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.pull-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.header-lg,
|
||||
.header-md,
|
||||
.header-sm {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
line-height: normal;
|
||||
padding: 35px 0 0;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
|
||||
.header-md {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.header-sm {
|
||||
padding: 5px 0;
|
||||
font-size: 18px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.content-padding {
|
||||
padding: 20px 0 30px;
|
||||
}
|
||||
|
||||
.mobile-header-padding-right {
|
||||
width: 290px;
|
||||
text-align: right;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.mobile-header-padding-left {
|
||||
width: 290px;
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.free-text {
|
||||
width: 100% !important;
|
||||
padding: 10px 60px 0px;
|
||||
}
|
||||
|
||||
.block-rounded {
|
||||
border-radius: 5px;
|
||||
border: 1px solid #e5e5e5;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 55px 0 0;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
padding: 0 20px;
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.mini-block-container {
|
||||
padding: 30px 50px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.mini-block {
|
||||
background-color: #ffffff;
|
||||
width: 498px;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 5px;
|
||||
padding: 60px 75px;
|
||||
}
|
||||
|
||||
.block-rounded {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.info-img {
|
||||
width: 258px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.force-width-img {
|
||||
width: 480px;
|
||||
height: 1px !important;
|
||||
}
|
||||
|
||||
.force-width-full {
|
||||
width: 600px;
|
||||
height: 1px !important;
|
||||
}
|
||||
|
||||
.user-img img {
|
||||
width: 82px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
.user-img {
|
||||
width: 92px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.user-msg {
|
||||
width: 236px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
padding: 10px 0;
|
||||
border: 1px solid #cccccc;
|
||||
color: #4d4d4d;
|
||||
font-weight: bold;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.force-width-gmail {
|
||||
min-width:600px;
|
||||
height: 0px !important;
|
||||
line-height: 1px !important;
|
||||
font-size: 1px !important;
|
||||
}
|
||||
|
||||
.button-width {
|
||||
width: 228px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
@import url(http://fonts.googleapis.com/css?family=Oxygen:400,700);
|
||||
</style>
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
@media screen {
|
||||
/* Thanks Outlook 2013! */
|
||||
* {
|
||||
font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style type="text/css" media="only screen and (max-width: 480px)">
|
||||
/* Mobile styles */
|
||||
@media only screen and (max-width: 480px) {
|
||||
|
||||
table[class*="container-for-gmail-android"] {
|
||||
min-width: 290px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class="w320"] {
|
||||
width: 320px !important;
|
||||
}
|
||||
|
||||
img[class="force-width-gmail"] {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
a[class="button-width"],
|
||||
a[class="button-mobile"] {
|
||||
width: 248px !important;
|
||||
}
|
||||
|
||||
td[class*="mobile-header-padding-left"] {
|
||||
width: 160px !important;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
td[class*="mobile-header-padding-right"] {
|
||||
width: 160px !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
td[class="header-lg"] {
|
||||
font-size: 24px !important;
|
||||
padding-bottom: 5px !important;
|
||||
}
|
||||
|
||||
td[class="header-md"] {
|
||||
font-size: 18px !important;
|
||||
padding-bottom: 5px !important;
|
||||
}
|
||||
|
||||
td[class="content-padding"] {
|
||||
padding: 5px 0 30px !important;
|
||||
}
|
||||
|
||||
td[class="button"] {
|
||||
padding: 15px 0 5px !important;
|
||||
}
|
||||
|
||||
td[class*="free-text"] {
|
||||
padding: 10px 18px 30px !important;
|
||||
}
|
||||
|
||||
img[class="force-width-img"],
|
||||
img[class="force-width-full"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
td[class="info-block"] {
|
||||
display: block !important;
|
||||
width: 280px !important;
|
||||
padding-bottom: 40px !important;
|
||||
}
|
||||
|
||||
td[class="info-img"],
|
||||
img[class="info-img"] {
|
||||
width: 278px !important;
|
||||
}
|
||||
|
||||
td[class="mini-block-container"] {
|
||||
padding: 8px 20px !important;
|
||||
width: 280px !important;
|
||||
}
|
||||
|
||||
td[class="mini-block"] {
|
||||
padding: 20px !important;
|
||||
}
|
||||
|
||||
td[class="user-img"] {
|
||||
display: block !important;
|
||||
text-align: center !important;
|
||||
width: 100% !important;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
td[class="user-msg"] {
|
||||
display: block !important;
|
||||
padding-bottom: 20px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body bgcolor="#f7f7f7">
|
||||
<table align="center" cellpadding="0" cellspacing="0" class="container-for-gmail-android" width="100%">
|
||||
<tr>
|
||||
<td align="left" valign="top" width="100%" style="background-color: #ffffff;">
|
||||
<center>
|
||||
<table cellspacing="0" cellpadding="0" width="100%" bgcolor="#ffffff" style="border-bottom: 1px solid #cccccc">
|
||||
<tr>
|
||||
<td width="100%" height="80" valign="top" style="text-align: center; vertical-align:middle;">
|
||||
<center>
|
||||
<table cellpadding="0" cellspacing="0" width="600" class="w320">
|
||||
<tr>
|
||||
<td style="vertical-align: middle;padding: 15px 0;">
|
||||
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
<!--[if gte mso 9]>
|
||||
</v:textbox>
|
||||
</v:rect>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="100%" style="background-color: #f7f7f7;" class="content-padding">
|
||||
<center>
|
||||
<table cellspacing="0" cellpadding="0" width="600" class="w320">
|
||||
<tr>
|
||||
<td class="header-lg">
|
||||
{{USER_INVITE_MATCH_1}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="free-text">
|
||||
{{USER_INVITE_MATCH_2}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mini-block-container">
|
||||
<table cellspacing="0" cellpadding="0" width="100%" style="border-collapse:separate !important;">
|
||||
<tr>
|
||||
<td class="mini-block">
|
||||
<table cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
<td style="padding-bottom: 30px;">
|
||||
{{USER_INVITE_MATCH_3}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="code-block">
|
||||
{{token}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="button">
|
||||
<div><a class="button-mobile" target="_blank" href="{{url}}/recover-password?email={{to}}&token={{token}}&invited=true"
|
||||
style="background-color:#ff6f6f;border-radius:5px;color:#ffffff;display:inline-block;font-family:'Cabin', Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;line-height:45px;text-align:center;text-decoration:none;width:155px;-webkit-text-size-adjust:none;mso-hide:all;">Set up your password</a></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="100%" style="background-color: #ffffff; border-top: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5; height: 100px;">
|
||||
<center>
|
||||
<table cellspacing="0" cellpadding="0" width="600" class="w320">
|
||||
<tr>
|
||||
<td style="padding: 25px 0 25px">
|
||||
<strong>OpenSupports</strong><br />
|
||||
Open source ticket system<br />
|
||||
www.opensupports.com<br /><br />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -19,6 +19,7 @@ class MailTemplate extends DataStore {
|
|||
const USER_SIGNUP = 'USER_SIGNUP';
|
||||
const USER_PASSWORD = 'USER_PASSWORD';
|
||||
const PASSWORD_FORGOT = 'PASSWORD_FORGOT';
|
||||
const USER_INVITE = 'USER_INVITE';
|
||||
const USER_SYSTEM_DISABLED = 'USER_SYSTEM_DISABLED';
|
||||
const USER_SYSTEM_ENABLED = 'USER_SYSTEM_ENABLED';
|
||||
const TICKET_CREATED = 'TICKET_CREATED';
|
||||
|
@ -32,6 +33,7 @@ class MailTemplate extends DataStore {
|
|||
'USER_PASSWORD' => 'data/mail-templates/user-edit-password.html',
|
||||
'USER_EMAIL' => 'data/mail-templates/user-edit-email.html',
|
||||
'PASSWORD_FORGOT' => 'data/mail-templates/user-password-forgot.html',
|
||||
'USER_INVITE' => 'data/mail-templates/user-invite.html',
|
||||
'USER_SYSTEM_DISABLED' => 'data/mail-templates/user-system-disabled.html',
|
||||
'USER_SYSTEM_ENABLED' => 'data/mail-templates/user-system-enabled.html',
|
||||
'TICKET_CREATED' => 'data/mail-templates/ticket-created.html',
|
||||
|
|
Loading…
Reference in New Issue