This commit is contained in:
Ivan Diaz 2019-01-18 20:58:30 -03:00
parent 08a6f7b8d0
commit 58c6f2e63f
13 changed files with 92 additions and 21 deletions

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import _ from 'lodash'; import _ from 'lodash';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import randomString from 'random-string';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call'; import API from 'lib-app/api-call';
@ -55,6 +56,7 @@ class AdminPanelEmailSettings extends React.Component {
['imap-host']: '', ['imap-host']: '',
['imap-user']: '', ['imap-user']: '',
['imap-pass']: 'HIDDEN', ['imap-pass']: 'HIDDEN',
['imap-token']: '',
}, },
}; };
@ -135,7 +137,7 @@ class AdminPanelEmailSettings extends React.Component {
<SubmitButton className="admin-panel-email-settings__submit" type="secondary" <SubmitButton className="admin-panel-email-settings__submit" type="secondary"
size="small">{i18n('SAVE')}</SubmitButton> size="small">{i18n('SAVE')}</SubmitButton>
<SubmitButton type="tertiary" size="small" onClick={this.testSMTP.bind(this)}> <SubmitButton type="tertiary" size="small" onClick={this.testSMTP.bind(this)}>
Test {i18n('TEST')}
</SubmitButton> </SubmitButton>
</div> </div>
</Form> </Form>
@ -148,11 +150,12 @@ class AdminPanelEmailSettings extends React.Component {
<FormField name="imap-host" label={i18n('IMAP_SERVER')} fieldProps={{size: 'large'}}/> <FormField name="imap-host" label={i18n('IMAP_SERVER')} fieldProps={{size: 'large'}}/>
<FormField name="imap-user" label={i18n('IMAP_USER')} fieldProps={{size: 'large'}}/> <FormField name="imap-user" label={i18n('IMAP_USER')} fieldProps={{size: 'large'}}/>
<FormField name="imap-pass" label={i18n('IMAP_PASSWORD')} fieldProps={{size: 'large'}}/> <FormField name="imap-pass" label={i18n('IMAP_PASSWORD')} fieldProps={{size: 'large'}}/>
<FormField name="imap-token" label={i18n('IMAP_TOKEN')} infoMessage={i18n('IMAP_TOKEN_DESCRIPTION')} fieldProps={{size: 'large', icon: 'refresh', onIconClick: this.generateImapToken.bind(this)}}/>
<div className="admin-panel-email-settings__server-form-buttons"> <div className="admin-panel-email-settings__server-form-buttons">
<SubmitButton className="admin-panel-email-settings__submit" type="secondary" <SubmitButton className="admin-panel-email-settings__submit" type="secondary"
size="small">{i18n('SAVE')}</SubmitButton> size="small">{i18n('SAVE')}</SubmitButton>
<SubmitButton type="tertiary" size="small" onClick={this.testIMAP.bind(this)}> <SubmitButton type="tertiary" size="small" onClick={this.testIMAP.bind(this)}>
Test {i18n('TEST')}
</SubmitButton> </SubmitButton>
</div> </div>
</Form> </Form>
@ -334,6 +337,15 @@ class AdminPanelEmailSettings extends React.Component {
AreYouSure.openModal(i18n('WILL_RECOVER_EMAIL_TEMPLATE'), this.recoverEmailTemplate.bind(this)); AreYouSure.openModal(i18n('WILL_RECOVER_EMAIL_TEMPLATE'), this.recoverEmailTemplate.bind(this));
} }
generateImapToken() {
this.setState({
imapForm: {
...this.state.imapForm,
['imap-token']: randomString({length: 20}),
}
});
}
submitEmailAddress(form) { submitEmailAddress(form) {
this.editSettings(form, 'EMAIL_SUCCESS'); this.editSettings(form, 'EMAIL_SUCCESS');
} }
@ -481,6 +493,7 @@ class AdminPanelEmailSettings extends React.Component {
['imap-host']: result.data['imap-host'], ['imap-host']: result.data['imap-host'],
['imap-user']: result.data['imap-user'], ['imap-user']: result.data['imap-user'],
['imap-pass']: 'HIDDEN', ['imap-pass']: 'HIDDEN',
['imap-token']: result.data['imap-token'],
}, },
})); }));
} }

View File

@ -18,7 +18,8 @@ class Input extends React.Component {
password: React.PropTypes.bool, password: React.PropTypes.bool,
required: React.PropTypes.bool, required: React.PropTypes.bool,
icon: React.PropTypes.string, icon: React.PropTypes.string,
error: React.PropTypes.string error: React.PropTypes.string,
onIconClick: React.PropTypes.func
}; };
static defaultProps = { static defaultProps = {
@ -38,7 +39,7 @@ class Input extends React.Component {
let icon = null; let icon = null;
if (this.props.icon) { if (this.props.icon) {
icon = <span className="input__icon"><Icon name={this.props.icon} /></span> icon = <span className="input__icon" onClick={this.onIconClick.bind(this)}><Icon name={this.props.icon} /></span>
} }
return icon; return icon;
@ -66,6 +67,7 @@ class Input extends React.Component {
'input': true, 'input': true,
'input_with-icon': (this.props.icon), 'input_with-icon': (this.props.icon),
'input_errored': (this.props.errored), 'input_errored': (this.props.errored),
'input_icon-clickable': (this.props.onIconClick),
['input_' + this.props.size]: true, ['input_' + this.props.size]: true,
[this.props.className]: (this.props.className) [this.props.className]: (this.props.className)
@ -74,6 +76,14 @@ class Input extends React.Component {
return classNames(classes); return classNames(classes);
} }
onIconClick(event) {
if(this.props.onIconClick) {
event.preventDefault();
this.focus();
this.props.onIconClick(event);
}
}
focus() { focus() {
if (this.refs.nativeInput) { if (this.refs.nativeInput) {
this.refs.nativeInput.focus(); this.refs.nativeInput.focus();

View File

@ -58,4 +58,11 @@
border: 1px solid $primary-red; border: 1px solid $primary-red;
} }
} }
&_icon-clickable {
.input__icon {
cursor: pointer;
}
}
} }

View File

@ -144,6 +144,8 @@ export default {
'IMAP_USER': 'IMAP User', 'IMAP_USER': 'IMAP User',
'IMAP_SERVER': 'IMAP Server', 'IMAP_SERVER': 'IMAP Server',
'IMAP_PASSWORD': 'IMAP Password', 'IMAP_PASSWORD': 'IMAP Password',
'IMAP_TOKEN': 'IMAP Token',
'IMAP_TOKEN_DESCRIPTION': 'Use this token to authenticate the polling request.',
'PORT': 'Port', 'PORT': 'Port',
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha Public Key', 'RECAPTCHA_PUBLIC_KEY': 'Recaptcha Public Key',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha Private Key', 'RECAPTCHA_PRIVATE_KEY': 'Recaptcha Private Key',
@ -382,6 +384,7 @@ export default {
'LAST_90_DAYS': 'Last 90 days', 'LAST_90_DAYS': 'Last 90 days',
'LAST_365_DAYS': 'Last 365 days', 'LAST_365_DAYS': 'Last 365 days',
'TEST': 'Test',
'ACTIVITY_COMMENT_THIS': 'commented this ticket', 'ACTIVITY_COMMENT_THIS': 'commented this ticket',
'ACTIVITY_ASSIGN_THIS': 'assigned this ticket to', 'ACTIVITY_ASSIGN_THIS': 'assigned this ticket to',
'ACTIVITY_UN_ASSIGN_THIS': 'unassigned this ticket to', 'ACTIVITY_UN_ASSIGN_THIS': 'unassigned this ticket to',

View File

@ -8,7 +8,9 @@
"ifsnop/mysqldump-php": "2.*", "ifsnop/mysqldump-php": "2.*",
"ezyang/htmlpurifier": "^4.8", "ezyang/htmlpurifier": "^4.8",
"codeguy/upload": "^1.3", "codeguy/upload": "^1.3",
"php-imap/php-imap": "^3.0" "php-imap/php-imap": "^3.0",
"willdurand/email-reply-parser": "^2.8",
"ext-fileinfo": "^1.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^5.7" "phpunit/phpunit": "^5.7"

View File

@ -31,5 +31,6 @@ $systemControllerGroup->addController(new DisableUserSystemController);
$systemControllerGroup->addController(new EnableUserSystemController); $systemControllerGroup->addController(new EnableUserSystemController);
$systemControllerGroup->addController(new TestSMTPController); $systemControllerGroup->addController(new TestSMTPController);
$systemControllerGroup->addController(new TestIMAPController); $systemControllerGroup->addController(new TestIMAPController);
$systemControllerGroup->addController(new EmailPollingController);
$systemControllerGroup->finalize(); $systemControllerGroup->finalize();

View File

@ -42,6 +42,7 @@ class EditSettingsController extends Controller {
'imap-host', 'imap-host',
'imap-user', 'imap-user',
'imap-pass', 'imap-pass',
'imap-token',
'smtp-host', 'smtp-host',
'smtp-user', 'smtp-user',
'smtp-pass', 'smtp-pass',

View File

@ -1,6 +1,7 @@
<?php <?php
use Respect\Validation\Validator as DataValidator;
class EmailPolling extends Controller { class EmailPollingController extends Controller {
const PATH = '/email-polling'; const PATH = '/email-polling';
const METHOD = 'POST'; const METHOD = 'POST';
@ -9,7 +10,12 @@ class EmailPolling extends Controller {
public function validations() { public function validations() {
return [ return [
'permission' => 'any', 'permission' => 'any',
'requestData' => [] 'requestData' => [
'token' => [
'validation' => DataValidator::length(1, 200),
'error' => ERRORS::INVALID_TOKEN
]
]
]; ];
} }
@ -19,6 +25,10 @@ class EmailPolling extends Controller {
$defaultLanguage = Setting::getSetting('language')->getValue(); $defaultLanguage = Setting::getSetting('language')->getValue();
$defaultDepartmentId = Department::getAll()->first()->id; $defaultDepartmentId = Department::getAll()->first()->id;
if(Controller::request('token') !== Setting::getSetting('imap-token')->getValue())
throw new RequestException(ERRORS::INVALID_TOKEN);
if(Controller::isUserSystemEnabled()) if(Controller::isUserSystemEnabled())
throw new RequestException(ERRORS::USER_SYSTEM); throw new RequestException(ERRORS::USER_SYSTEM);
@ -26,12 +36,12 @@ class EmailPolling extends Controller {
Setting::getSetting('imap-host')->getValue(), Setting::getSetting('imap-host')->getValue(),
Setting::getSetting('imap-user')->getValue(), Setting::getSetting('imap-user')->getValue(),
Setting::getSetting('imap-pass')->getValue(), Setting::getSetting('imap-pass')->getValue(),
__DIR__ 'files/'
); );
$errors = []; $errors = [];
$emails = $this->getLastEmails(); $emails = $this->getLastEmails();
/*
$session = Session::getInstance(); $session = Session::getInstance();
$oldSession = [ $oldSession = [
'userId' => $session->getUserId(), 'userId' => $session->getUserId(),
@ -61,6 +71,17 @@ class EmailPolling extends Controller {
return null; return null;
}); });
if($email->getAttachement()) {
$attachment = $email->getAttachement();
$_FILES['file'] = [
'name' => $attachment->name,
'type' => mime_content_type($attachment->filePath),
'tmp_name' => $attachment->filePath,
'error' => UPLOAD_ERR_OK,
'size' => filesize($attachment->filePath),
];
}
try { try {
if($email->isReply()) { if($email->isReply()) {
if($email->getTicket()->authorToArray()['email'] === $email->getSender()) { if($email->getTicket()->authorToArray()['email'] === $email->getSender()) {
@ -79,6 +100,8 @@ class EmailPolling extends Controller {
'error' => $e->__toString(), 'error' => $e->__toString(),
]; ];
} }
unset($_FILES['file']);
} }
$session->clearSessionData(); $session->clearSessionData();
@ -90,7 +113,7 @@ class EmailPolling extends Controller {
Response::respondError(ERRORS::EMAIL_POLLING, null, $errors); Response::respondError(ERRORS::EMAIL_POLLING, null, $errors);
} else { } else {
Response::respondSuccess(); Response::respondSuccess();
} }*/
} }
public function getLastEmails() { public function getLastEmails() {
@ -101,12 +124,14 @@ class EmailPolling extends Controller {
foreach($mailsIds as $mailId) { foreach($mailsIds as $mailId) {
$mail = $this->mailbox->getMail($mailId); $mail = $this->mailbox->getMail($mailId);
$mailHeader = $this->mailbox->getMailHeader($mailId); $mailHeader = $this->mailbox->getMailHeader($mailId);
$mailAttachment = count($mail->getAttachments()) ? $mail->getAttachments()[0] : null;
$emails[] = new Email([ $emails[] = new Email([
'fromAddress' => $mailHeader->fromAddress, 'fromAddress' => $mailHeader->fromAddress,
'fromName' => $mailHeader->fromName, 'fromName' => $mailHeader->fromName,
'subject' => $mailHeader->subject, 'subject' => $mailHeader->subject,
'content' => $mail->textPlain, 'content' => $mail->textPlain,
'file' => null, 'file' => $mailAttachment,
]); ]);
} }

View File

@ -50,6 +50,7 @@ class GetSettingsController extends Controller {
'smtp-user' => Setting::getSetting('smtp-user')->getValue(), 'smtp-user' => Setting::getSetting('smtp-user')->getValue(),
'imap-host' => Setting::getSetting('imap-host')->getValue(), 'imap-host' => Setting::getSetting('imap-host')->getValue(),
'imap-user' => Setting::getSetting('imap-user')->getValue(), 'imap-user' => Setting::getSetting('imap-user')->getValue(),
'imap-token' => Setting::getSetting('imap-token')->getValue(),
'registration' => Setting::getSetting('registration')->getValue(), 'registration' => Setting::getSetting('registration')->getValue(),
'departments' => Department::getAllDepartmentNames(), 'departments' => Department::getAllDepartmentNames(),
'supportedLanguages' => Language::getSupportedLanguages(), 'supportedLanguages' => Language::getSupportedLanguages(),

View File

@ -88,7 +88,8 @@ class InitSettingsController extends Controller {
'ticket-gap' => Hashing::generateRandomPrime(100000, 999999), 'ticket-gap' => Hashing::generateRandomPrime(100000, 999999),
'ticket-first-number' => Hashing::generateRandomNumber(100000, 999999), 'ticket-first-number' => Hashing::generateRandomNumber(100000, 999999),
'session-prefix' => 'opensupports-'.Hashing::generateRandomToken().'_', 'session-prefix' => 'opensupports-'.Hashing::generateRandomToken().'_',
'mail-template-header-image' => 'https://s3.amazonaws.com/opensupports/logo.png' 'mail-template-header-image' => 'https://s3.amazonaws.com/opensupports/logo.png',
'imap-token' => '',
]); ]);
} }

View File

@ -79,7 +79,7 @@ class CommentController extends Controller {
public function handler() { public function handler() {
$this->requestData(); $this->requestData();
$ticketAuthor = $this->ticket->authorToArray(); $ticketAuthor = $this->ticket->authorToArray();
$isAuthor = $this->ticket->isAuthor(Controller::getLoggedUser()); $isAuthor = $this->ticket->isAuthor(Controller::getLoggedUser()) || Session::getInstance()->isTicketSession();
$isOwner = $this->ticket->isOwner(Controller::getLoggedUser()); $isOwner = $this->ticket->isOwner(Controller::getLoggedUser());
if((Controller::isUserSystemEnabled() || Controller::isStaffLogged()) && !$isOwner && !$isAuthor) { if((Controller::isUserSystemEnabled() || Controller::isStaffLogged()) && !$isOwner && !$isAuthor) {
@ -89,13 +89,13 @@ class CommentController extends Controller {
$this->storeComment(); $this->storeComment();
if($isAuthor && $this->ticket->owner) { if($isAuthor && $this->ticket->owner) {
$this->sendMail([ $this->sendMail([
'email' => $this->ticket->owner->email, 'email' => $this->ticket->owner->email,
'name' => $this->ticket->owner->name, 'name' => $this->ticket->owner->name,
'staff' => true 'staff' => true
]); ]);
} else { } else if($isOwner) {
$this->sendMail($ticketAuthor); $this->sendMail($ticketAuthor);
} }
Log::createLog('COMMENT', $this->ticket->ticketNumber); Log::createLog('COMMENT', $this->ticket->ticketNumber);

View File

@ -1,4 +1,5 @@
<?php <?php
use EmailReplyParser\Parser\EmailParser;
class Email { class Email {
private $sender; private $sender;
@ -49,7 +50,9 @@ class Email {
} }
private function parseContent($data) { private function parseContent($data) {
return $data['content']; $emailParser = new EmailParser();
return $emailParser->parse($data['content'])->getVisibleText();
} }
private function parseAttachment($data) { private function parseAttachment($data) {

View File

@ -41,6 +41,10 @@ class Session {
$this->store('token', Hashing::generateRandomToken()); $this->store('token', Hashing::generateRandomToken());
} }
public function isTicketSession() {
return $this->getStoredData('ticketNumber') && $this->getStoredData('token');
}
public function getTicketNumber() { public function getTicketNumber() {
return $this->getStoredData('ticketNumber'); return $this->getStoredData('ticketNumber');
} }