Merge branch 'master' into master

This commit is contained in:
Guillermo Giuliana 2018-11-23 21:36:40 -03:00 committed by GitHub
commit 80b6bcea8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 1208 additions and 775 deletions

View File

@ -4,6 +4,7 @@ php:
- '5.6'
- '7.0'
- '7.1'
- '7.2'
services:
- mysql

View File

@ -69,7 +69,8 @@ describe('Session Actions,', function () {
data: {
rememberToken: 'SOME_TOKEN',
userId: 'SOME_ID',
isAutomatic: true
remember: 1,
isAutomatic: 1
}
});
});

View File

@ -12,32 +12,32 @@ export default {
};
},
retrieveMyTickets() {
retrieveMyTickets(page, closed = 0) {
return {
type: 'MY_TICKETS',
payload: API.call({
path: '/staff/get-tickets',
data: {}
data: {page, closed}
})
};
},
retrieveNewTickets() {
retrieveNewTickets(page = 1) {
return {
type: 'NEW_TICKETS',
payload: API.call({
path: '/staff/get-new-tickets',
data: {}
data: {page}
})
};
},
retrieveAllTickets(page = 1) {
retrieveAllTickets(page = 1, query = '', closed = 0) {
return {
type: 'ALL_TICKETS',
payload: API.call({
path: '/staff/get-all-tickets',
data: {page}
data: {page, query, closed}
})
};
},

View File

@ -1,3 +1,5 @@
import _ from 'lodash';
import API from 'lib-app/api-call';
import AdminDataActions from 'actions/admin-data-actions';
import sessionStore from 'lib-app/session-store';
@ -12,7 +14,7 @@ export default {
let loginCall = () => {
API.call({
path: '/user/login',
data: loginData
data: _.extend(loginData, {remember: loginData.remember * 1})
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff)).then(() => {
if(result.data.staff) {
@ -48,7 +50,8 @@ export default {
data: {
userId: rememberData.userId,
rememberToken: rememberData.token,
isAutomatic: true
remember: 1,
isAutomatic: 1
}
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId, result.data.token));

View File

@ -96,6 +96,10 @@
border-top: none;
padding: 20px 10px;
text-align: left;
img {
max-width:100%;
}
}
}

View File

@ -3,6 +3,7 @@
.ticket-info {
width: 300px;
font-weight: normal;
text-align: left;
&__title {
color: $primary-black;
@ -86,4 +87,4 @@
}
}
}
}
}

View File

@ -10,6 +10,7 @@ import Table from 'core-components/table';
import Button from 'core-components/button';
import Tooltip from 'core-components/tooltip';
import Icon from 'core-components/icon';
import Checkbox from 'core-components/checkbox';
class TicketList extends React.Component {
static propTypes = {
@ -22,7 +23,9 @@ class TicketList extends React.Component {
type: React.PropTypes.oneOf([
'primary',
'secondary'
])
]),
closedTicketsShown: React.PropTypes.bool,
onClosedTicketsShownChange: React.PropTypes.func
};
static defaultProps = {
@ -31,7 +34,8 @@ class TicketList extends React.Component {
tickets: [],
departments: [],
ticketPath: '/dashboard/ticket/',
type: 'primary'
type: 'primary',
closedTicketsShown: false
};
state = {
@ -41,12 +45,21 @@ class TicketList extends React.Component {
render() {
return (
<div className="ticket-list">
{(this.props.type === 'secondary' && this.props.showDepartmentDropdown) ? this.renderDepartmentsDropDown() : null}
<div className="ticket-list__filters">
{(this.props.type === 'secondary' && this.props.showDepartmentDropdown) ? this.renderDepartments
() : null}
{this.props.onClosedTicketsShownChange ? this.renderFilterCheckbox() : null}
</div>
<Table {...this.getTableProps()} />
</div>
);
}
renderFilterCheckbox() {
return <Checkbox className="ticket-list__checkbox" label={i18n("SHOW_CLOSED_TICKETS")} value={this.props.closedTicketsShown} onChange={this.props.onClosedTicketsShownChange} wrapInLabel/>
}
renderDepartmentsDropDown() {
return (
<div className="ticket-list__department-selector">

View File

@ -2,8 +2,19 @@
.ticket-list {
&__department-selector {
&__filters {
margin-bottom: 25px;
text-align: left;
}
&__department-selector {
display: inline-block;
margin-right: 25px;
text-align: center;
}
&__checkbox {
display: inline-block;
}
&__number {
@ -52,4 +63,4 @@
&__priority-high {
background-color: $primary-red;
}
}
}

View File

@ -7,6 +7,7 @@ import AdminDataActions from 'actions/admin-data-actions';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import SessionStore from 'lib-app/session-store';
import MentionsParser from 'lib-app/mentions-parser';
import TicketEvent from 'app-components/ticket-event';
import AreYouSure from 'app-components/are-you-sure';
@ -74,7 +75,7 @@ class TicketViewer extends React.Component {
</div>
{this.props.editable ? this.renderEditableHeaders() : this.renderHeaders()}
<div className="ticket-viewer__content">
<TicketEvent type="COMMENT" author={ticket.author} content={ticket.content} date={ticket.date} file={ticket.file}/>
<TicketEvent type="COMMENT" author={ticket.author} content={this.props.userStaff ? MentionsParser.parse(ticket.content) : ticket.content} date={ticket.date} file={ticket.file}/>
</div>
<div className="ticket-viewer__comments">
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
@ -208,6 +209,9 @@ class TicketViewer extends React.Component {
}
renderTicketEvent(options, index) {
if (this.props.userStaff) {
options.content = MentionsParser.parse(options.content);
}
return (
<TicketEvent {...options} author={(!_.isEmpty(options.author)) ? options.author : this.props.ticket.author} key={index} />
);

View File

@ -7,6 +7,7 @@ import ArticlesActions from 'actions/articles-actions';
import SessionStore from 'lib-app/session-store';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import MentionsParser from 'lib-app/mentions-parser';
import DateTransformer from 'lib-core/date-transformer';
import AreYouSure from 'app-components/are-you-sure';
@ -76,7 +77,7 @@ class AdminPanelViewArticle extends React.Component {
<Header title={article.title}/>
<div className="admin-panel-view-article__article-content">
<div dangerouslySetInnerHTML={{__html: article.content}}/>
<div dangerouslySetInnerHTML={{__html: MentionsParser.parse(article.content)}}/>
</div>
<div className="admin-panel-view-article__last-edited">
{i18n('LAST_EDITED_IN', {date: DateTransformer.transformToString(article.lastEdited)})}

View File

@ -1,5 +1,6 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
@ -17,66 +18,58 @@ import SubmitButton from 'core-components/submit-button';
class AdminPanelEmailTemplates extends React.Component {
static propTypes = {
language: React.PropTypes.string,
};
state = {
loaded: false,
items: [],
formLoading: false,
selectedIndex: 0,
headerImage: '',
loadingHeaderImage: false,
loadingList: true,
loadingTemplate: false,
templates: [],
loadingForm: false,
selectedIndex: -1,
edited: false,
errors: {},
language: 'en',
language: this.props.language,
form: {
title: '',
content: ''
subject: '',
text1: '',
text2: '',
text3: '',
}
};
componentDidMount() {
this.retrieveEmailTemplates();
this.retrieveMailTemplateList();
this.retrieveHeaderImage();
}
render() {
return (
<div className="admin-panel-email-templates">
<Header title={i18n('EMAIL_TEMPLATES')} description={i18n('EMAIL_TEMPLATES_DESCRIPTION')} />
{(this.state.loaded) ? this.renderContent() : this.renderLoading()}
{(!this.state.loadingList) ? this.renderContent() : this.renderLoading()}
</div>
);
}
renderContent() {
return (
<div className="row">
<div className="col-md-3">
<Listing {...this.getListingProps()}/>
</div>
<div className="col-md-9">
<FormField label={i18n('LANGUAGE')} decorator={LanguageSelector} value={this.state.language} onChange={event => this.onItemChange(this.state.selectedIndex, event.target.value)} fieldProps={{
type: 'allowed',
size: 'medium'
}}/>
<Form {...this.getFormProps()}>
<div className="row">
<div className="col-md-7">
<FormField label={i18n('TITLE')} name="title" validation="TITLE" required fieldProps={{size: 'large'}}/>
</div>
</div>
<FormField label={i18n('CONTENT')} name="content" validation="TEXT_AREA" required decorator={'textarea'} fieldProps={{className: 'admin-panel-email-templates__text-area'}} />
<div className="admin-panel-email-templates__actions">
<div className="admin-panel-email-templates__save-button">
<SubmitButton type="secondary" size="small">{i18n('SAVE')}</SubmitButton>
</div>
<div className="admin-panel-email-templates__optional-buttons">
{(this.state.edited) ? this.renderDiscardButton() : null}
<div className="admin-panel-email-templates__recover-button">
<Button onClick={this.onRecoverClick.bind(this)} size="medium">
{i18n('RECOVER_DEFAULT')}
</Button>
</div>
</div>
</div>
</Form>
<div>
<div className="row">
<div className="col-md-3">
<Listing {...this.getListingProps()}/>
</div>
{(this.state.selectedIndex != -1) ? this.renderForm() : null}
</div>
<Form values={{headerImage: this.state.headerImage}} onChange={form => this.setState({headerImage: form.headerImage})} onSubmit={this.onHeaderImageSubmit.bind(this)}>
<div className="admin-panel-email-templates__image-container">
<FormField className="admin-panel-email-templates__image-header-url" label={i18n('IMAGE_HEADER_URL')} name="headerImage" required fieldProps={{size: 'large'}} />
<SubmitButton className="admin-panel-email-templates__image-header-submit" type="secondary" size="small">{i18n('SAVE')}</SubmitButton>
</div>
</Form>
</div>
);
}
@ -89,6 +82,42 @@ class AdminPanelEmailTemplates extends React.Component {
);
}
renderForm() {
return (
<div className="col-md-9">
<FormField label={i18n('LANGUAGE')} decorator={LanguageSelector} value={this.state.language} onChange={event => this.onItemChange(this.state.selectedIndex, event.target.value)} fieldProps={{
type: 'allowed',
size: 'medium'
}}/>
<Form {...this.getFormProps()}>
<div className="row">
<div className="col-md-7">
<FormField label={i18n('SUBJECT')} name="subject" validation="TITLE" required fieldProps={{size: 'large'}}/>
</div>
</div>
<FormField label={i18n('TEXT') + '1'} name="text1" validation="TEXT_AREA" required decorator={'textarea'} fieldProps={{className: 'admin-panel-email-templates__text-area'}} />
{(this.state.form.text2) ? <FormField label={i18n('TEXT') + '2'} name="text2" validation="TEXT_AREA" required decorator={'textarea'} fieldProps={{className: 'admin-panel-email-templates__text-area'}} /> : null}
{(this.state.form.text3) ? <FormField label={i18n('TEXT') + '3'} name="text3" validation="TEXT_AREA" required decorator={'textarea'} fieldProps={{className: 'admin-panel-email-templates__text-area'}} /> : null}
<div className="admin-panel-email-templates__actions">
<div className="admin-panel-email-templates__save-button">
<SubmitButton type="secondary" size="small">{i18n('SAVE')}</SubmitButton>
</div>
<div className="admin-panel-email-templates__optional-buttons">
{(this.state.edited) ? this.renderDiscardButton() : null}
<div className="admin-panel-email-templates__recover-button">
<Button onClick={this.onRecoverClick.bind(this)} size="medium">
{i18n('RECOVER_DEFAULT')}
</Button>
</div>
</div>
</div>
</Form>
</div>
);
}
renderDiscardButton() {
return (
<div className="admin-panel-email-templates__discard-button">
@ -102,7 +131,7 @@ class AdminPanelEmailTemplates extends React.Component {
getListingProps() {
return {
title: i18n('EMAIL_TEMPLATES'),
items: this.getItems(),
items: this.getTemplateItems(),
selectedIndex: this.state.selectedIndex,
onChange: this.onItemChange.bind(this)
};
@ -112,49 +141,94 @@ class AdminPanelEmailTemplates extends React.Component {
return {
values: this.state.form,
errors: this.state.errors,
loading: this.state.formLoading,
loading: this.state.loadingForm,
onChange: (form) => {this.setState({form, edited: true})},
onValidateErrors: (errors) => {this.setState({errors})},
onSubmit: this.onFormSubmit.bind(this)
}
}
getItems() {
return this.state.items.map((item) => {
getTemplateItems() {
return this.state.templates.map((template) => {
return {
content: item.type
content: template
};
});
}
onItemChange(index, language) {
if(this.state.edited) {
AreYouSure.openModal(i18n('WILL_LOSE_CHANGES'), this.updateForm.bind(this, index, language));
AreYouSure.openModal(i18n('WILL_LOSE_CHANGES'), this.retrieveEmailTemplate.bind(this, index, language || this.state.language));
} else {
this.updateForm(index, language);
this.retrieveEmailTemplate(index, language || this.state.language);
}
}
onHeaderImageSubmit(form) {
this.setState({
loadingHeaderImage: true,
});
API.call({
path: '/system/edit-settings',
data: {
'mail-template-header-image': form['headerImage']
}
}).then(() => this.setState({
loadingHeaderImage: false,
}))
}
onFormSubmit(form) {
this.setState({formLoading: true});
const {selectedIndex, language, templates} = this.state;
this.setState({loadingForm: true});
API.call({
path: '/system/edit-mail-template',
data: {
templateType: this.state.items[this.state.selectedIndex].type,
subject: form.title,
body: form.content,
language: this.state.language
template: templates[selectedIndex],
language,
subject: form.subject,
text1: form.text1,
text2: form.text2,
text3: form.text3,
}
}).then(() => {
this.setState({formLoading: false});
this.retrieveEmailTemplates();
this.setState({loadingForm: false, edited: false});
}).catch(response => {
this.setState({
loadingForm: false,
});
switch(response.message) {
case 'INVALID_SUBJECT':
this.setState({
errors: {subject: i18n('INVALID_SYNTAX')}
});
break;
case 'INVALID_TEXT_1':
this.setState({
errors: {text1: i18n('INVALID_SYNTAX')}
});
break;
case 'INVALID_TEXT_2':
this.setState({
errors: {text2: i18n('INVALID_SYNTAX')}
});
break;
case 'INVALID_TEXT_3':
this.setState({
errors: {text3: i18n('INVALID_SYNTAX')}
});
break;
}
});
}
onDiscardChangesClick(event) {
event.preventDefault();
this.onItemChange(this.state.selectedIndex);
this.onItemChange(this.state.selectedIndex, this.state.language);
}
onRecoverClick(event) {
@ -163,74 +237,59 @@ class AdminPanelEmailTemplates extends React.Component {
}
recoverEmailTemplate() {
const {selectedIndex, language, templates} = this.state;
API.call({
path: '/system/recover-mail-template',
data: {
templateType: this.state.items[this.state.selectedIndex].type,
language: this.state.language
template: templates[selectedIndex],
language
}
}).then(() => {
this.retrieveEmailTemplates();
this.retrieveEmailTemplate(this.state.selectedIndex, language);
});
}
updateForm(index, language) {
let form = _.clone(this.state.form);
let items = this.state.items;
language = language || this.state.language;
form.title = (items[index] && items[index][language].subject) || '';
form.content = (items[index] && items[index][language].body) || '';
retrieveEmailTemplate(index, language) {
this.setState({
selectedIndex: index,
language: language,
edited: false,
formLoading: false,
form: form,
errors: {}
loadingForm: true,
});
API.call({
path: '/system/get-mail-template',
data: {template: this.state.templates[index], language}
}).then((result) => this.setState({
language,
selectedIndex: index,
edited: false,
loadingForm: false,
form: result.data,
errors: {},
}));
}
retrieveEmailTemplates() {
return API.call({
path: '/system/get-mail-templates',
retrieveMailTemplateList() {
API.call({
path: '/system/get-mail-template-list',
data: {}
}).then((result) => this.setState({
edited: false,
loaded: true,
items: this.getParsedItems(result.data)
}, this.updateForm.bind(this, this.state.selectedIndex)));
loadingList: false,
templates: result.data
}));
}
getParsedItems(items) {
let parsedItems = {};
_.forEach(items, (item) => {
if(parsedItems[item.type]) {
parsedItems[item.type][item.language] = {
subject: item.subject,
body: item.body
};
} else {
parsedItems[item.type] = {
[item.language]: {
subject: item.subject,
body: item.body
}
};
}
});
parsedItems = Object.keys(parsedItems).map((type) => {
return _.extend({
type: type
}, parsedItems[type]);
});
return parsedItems;
retrieveHeaderImage() {
API.call({
path: '/system/get-settings',
data: {allSettings: 1}
}).then(result => this.setState({
headerImage: result.data['mail-template-header-image']
}));
}
}
export default AdminPanelEmailTemplates;
export default connect((store) => {
return {
language: store.config.language,
};
})(AdminPanelEmailTemplates);

View File

@ -1,8 +1,10 @@
@import "../../../../scss/vars";
.admin-panel-email-templates {
&__text-area {
width: 100%;
height: 157px;
height: 45px;
}
&__save-button {
@ -23,4 +25,13 @@
display: inline-block;
margin-left: 10px;
}
}
&__image-container {
background-color: $very-light-grey;
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 20px;
margin-top: 20px;
}
}

View File

@ -112,7 +112,7 @@ class AdminPanelSystemPreferences extends React.Component {
<FormField className="admin-panel-system-preferences__file-attachments-field" name="allow-attachments" decorator={ToggleButton}/>
</div>
<div className="admin-panel-system-preferences__max-size">
<span>{i18n('MAX_SIZE_KB')}</span>
<span>{i18n('MAX_SIZE_MB')}</span>
<FormField className="admin-panel-system-preferences__max-size-field" fieldProps={{size: 'small'}} name="max-size"/>
</div>
</div>

View File

@ -20,11 +20,12 @@ class AdminPanelAllTickets extends React.Component {
state = {
page: 1,
query: ''
query: '',
closedTicketsShown: 0
};
componentDidMount() {
this.props.dispatch(AdminDataAction.retrieveAllTickets());
this.updateTicketList();
}
render() {
@ -41,6 +42,14 @@ class AdminPanelAllTickets extends React.Component {
);
}
updateTicketList() {
this.props.dispatch(AdminDataAction.retrieveAllTickets(
this.state.page,
this.state.query,
this.state.closedTicketsShown * 1
));
}
getTicketListProps() {
return {
userId: this.props.userId,
@ -52,28 +61,32 @@ class AdminPanelAllTickets extends React.Component {
ticketPath: '/admin/panel/tickets/view-ticket/',
onPageChange: this.onPageChange.bind(this),
page: this.state.page,
pages: this.props.pages
pages: this.props.pages,
closedTicketsShown: this.state.closedTicketsShown,
onClosedTicketsShownChange: this.onClosedTicketsShownChange.bind(this)
};
}
onSearch(query) {
this.setState({query, page: 1});
onClosedTicketsShownChange() {
this.setState(function(state) {
return {
closedTicketsShown: !state.closedTicketsShown
};
}, () => {
this.updateTicketList();
});
}
if(query) {
this.props.dispatch(AdminDataAction.searchTickets(query));
} else {
this.props.dispatch(AdminDataAction.retrieveAllTickets());
}
onSearch(query) {
this.setState({query, page: 1}, () => {
this.updateTicketList();
});
}
onPageChange(event) {
this.setState({page: event.target.value});
if(this.state.query) {
this.props.dispatch(AdminDataAction.searchTickets(this.state.query, event.target.value));
} else {
this.props.dispatch(AdminDataAction.retrieveAllTickets(event.target.value));
}
this.setState({page: event.target.value}, () => {
this.updateTicketList();
});
}
}

View File

@ -21,21 +21,27 @@ import TextEditor from 'core-components/text-editor';
class AdminPanelCustomResponses extends React.Component {
static defaultProps = {
items: []
items: [],
};
state = {
formClicked: false,
showForm: false,
formLoading: false,
selectedIndex: -1,
edited: false,
errors: {},
originalForm: {
title: '',
content: TextEditor.createEmpty(),
language: this.props.language
},
form: {
title: '',
content: TextEditor.createEmpty(),
language: 'en'
language: this.props.language
}
};
componentDidMount() {
if (!this.props.loaded) {
this.retrieveCustomResponses();
@ -57,25 +63,7 @@ class AdminPanelCustomResponses extends React.Component {
<div className="col-md-3">
<Listing {...this.getListingProps()}/>
</div>
<div className="col-md-9">
<Form {...this.getFormProps()}>
<div className="row">
<div className="col-md-7">
<FormField label={i18n('TITLE')} name="title" validation="TITLE" required fieldProps={{size: 'large'}}/>
</div>
<div className="col-md-5">
<FormField label={i18n('LANGUAGE')} name="language" field="input" decorator={LanguageSelector} fieldProps={{size: 'medium'}} />
</div>
</div>
<FormField label={i18n('CONTENT')} name="content" validation="TEXT_AREA" required field="textarea" />
<div className="admin-panel-custom-responses__actions">
<div className="admin-panel-custom-responses__save-button">
<SubmitButton type="secondary" size="small">{i18n('SAVE')}</SubmitButton>
</div>
{(this.state.selectedIndex !== -1) ? this.renderOptionalButtons() : null}
</div>
</Form>
</div>
{this.state.showForm ? this.renderForm() : null}
</div>
);
}
@ -88,11 +76,35 @@ class AdminPanelCustomResponses extends React.Component {
);
}
renderForm() {
return (
<div className="col-md-9">
<Form {...this.getFormProps()}>
<div className="row">
<div className="col-md-7">
<FormField label={i18n('TITLE')} name="title" validation="TITLE" required fieldProps={{size: 'large'}}/>
</div>
<div className="col-md-5">
<FormField label={i18n('LANGUAGE')} name="language" field="input" decorator={LanguageSelector} fieldProps={{size: 'medium'}} />
</div>
</div>
<FormField label={i18n('CONTENT')} name="content" validation="TEXT_AREA" required field="textarea" />
<div className="admin-panel-custom-responses__actions">
<div className="admin-panel-custom-responses__save-button">
<SubmitButton type="secondary" size="small">{i18n('SAVE')}</SubmitButton>
</div>
{(this.state.selectedIndex !== -1) ? this.renderOptionalButtons() : null}
</div>
</Form>
</div>
);
}
renderOptionalButtons() {
return (
<div className="admin-panel-custom-responses__optional-buttons">
<div className="admin-panel-custom-responses__discard-button">
<Button onClick={this.onDiscardChangesClick.bind(this)}>{i18n('DISCARD_CHANGES')}</Button>
{this.isEdited() ? <Button onClick={this.onDiscardChangesClick.bind(this)}>{i18n('DISCARD_CHANGES')}</Button> : null}
</div>
<div className="admin-panel-custom-responses__delete-button">
<Button onClick={this.onDeleteClick.bind(this)}>{i18n('DELETE')}</Button>
@ -117,7 +129,8 @@ class AdminPanelCustomResponses extends React.Component {
values: this.state.form,
errors: this.state.errors,
loading: this.state.formLoading,
onChange: (form) => {this.setState({form, edited: true})},
onClick: () => this.setState({formClicked: true}),
onChange: (form) => this.setState({form}),
onValidateErrors: (errors) => {this.setState({errors})},
onSubmit: this.onFormSubmit.bind(this)
}
@ -139,7 +152,7 @@ class AdminPanelCustomResponses extends React.Component {
}
onItemChange(index) {
if(this.state.edited) {
if(this.isEdited()) {
AreYouSure.openModal(i18n('WILL_LOSE_CHANGES'), this.updateForm.bind(this, index));
} else {
this.updateForm(index);
@ -147,28 +160,30 @@ class AdminPanelCustomResponses extends React.Component {
}
onFormSubmit(form) {
const {items, allowedLanguages} = this.props;
this.setState({formLoading: true});
if(this.state.selectedIndex !== -1) {
API.call({
path: '/ticket/edit-custom-response',
data: {
id: this.props.items[this.state.selectedIndex].id,
name: form.name,
id: items[this.state.selectedIndex].id,
name: form.title,
content: form.content,
language: form.language
language: _.includes(allowedLanguages, form.language) ? form.language : allowedLanguages[0]
}
}).then(() => {
this.setState({formLoading: false});
this.setState({formLoading: false, originalForm: form});
this.retrieveCustomResponses();
}).catch(this.onItemChange.bind(this, -1));
} else {
this.setState({form, originalForm: form});
API.call({
path: '/ticket/add-custom-response',
data: {
name: form.title,
content: form.content,
language: form.language
language: _.includes(allowedLanguages, form.language) ? form.language : allowedLanguages[0]
}
}).then(() => {
this.retrieveCustomResponses();
@ -204,12 +219,14 @@ class AdminPanelCustomResponses extends React.Component {
form.title = (this.props.items[index] && this.props.items[index].name) || '';
form.content = TextEditor.getEditorStateFromHTML((this.props.items[index] && this.props.items[index].content) || '');
form.language = (this.props.items[index] && this.props.items[index].language) || 'en';
form.language = (this.props.items[index] && this.props.items[index].language) || this.props.language;
this.setState({
formClicked: false,
showForm: true,
selectedIndex: index,
edited: false,
formLoading: false,
originalForm: form,
form: form,
errors: {}
});
@ -217,14 +234,21 @@ class AdminPanelCustomResponses extends React.Component {
retrieveCustomResponses() {
this.props.dispatch(AdminDataActions.retrieveCustomResponses());
this.setState({
edited: false
});
}
isEdited() {
return this.state.form.title && this.state.formClicked && (
this.state.form.title != this.state.originalForm.title ||
this.state.form.content != this.state.originalForm.content ||
this.state.form.language != this.state.originalForm.language
);
}
}
export default connect((store) => {
return {
allowedLanguages: store.config.allowedLanguages,
language: store.config.language,
loaded: store.adminData.customResponsesLoaded,
items: store.adminData.customResponses
};

View File

@ -18,11 +18,17 @@ class AdminPanelMyTickets extends React.Component {
static defaultProps = {
userId: 0,
departments: [],
tickets: []
tickets: [],
page: 1,
pages: 0,
};
state = {
closedTicketsShown: false,
};
componentDidMount() {
this.props.dispatch(AdminDataAction.retrieveMyTickets());
this.retrieveMyTickets()
}
render() {
@ -46,10 +52,23 @@ class AdminPanelMyTickets extends React.Component {
tickets: this.props.tickets,
type: 'secondary',
loading: this.props.loading,
ticketPath: '/admin/panel/tickets/view-ticket/'
ticketPath: '/admin/panel/tickets/view-ticket/',
closedTicketsShown: this.state.closedTicketsShown,
onClosedTicketsShownChange: this.onClosedTicketsShownChange.bind(this),
pages: this.props.pages,
page: this.props.page,
onPageChange: event => this.retrieveMyTickets(event.target.value)
};
}
onClosedTicketsShownChange() {
this.setState(function(state) {
return {
closedTicketsShown: !state.closedTicketsShown
};
}, () => this.retrieveMyTickets());
}
onCreateTicket() {
ModalContainer.openModal(
<div>
@ -63,7 +82,11 @@ class AdminPanelMyTickets extends React.Component {
onCreateTicketSuccess() {
ModalContainer.closeModal();
this.props.dispatch(AdminDataAction.retrieveMyTickets());
this.retrieveMyTickets();
}
retrieveMyTickets(page = this.props.page, closed = this.state.closedTicketsShown) {
this.props.dispatch(AdminDataAction.retrieveMyTickets(page, closed * 1));
}
}
@ -72,6 +95,8 @@ export default connect((store) => {
userId: store.session.userId,
departments: store.session.userDepartments,
tickets: store.adminData.myTickets,
page: store.adminData.myTicketsPage,
pages: store.adminData.myTicketsPages,
loading: !store.adminData.myTicketsLoaded,
error: store.adminData.myTicketsError
};

View File

@ -12,13 +12,14 @@ import Message from 'core-components/message';
class AdminPanelNewTickets extends React.Component {
static defaultProps = {
page: 1,
userId: 0,
departments: [],
tickets: []
};
componentDidMount() {
this.props.dispatch(AdminDataAction.retrieveNewTickets());
this.retrieveNewTickets();
}
render() {
@ -39,9 +40,16 @@ class AdminPanelNewTickets extends React.Component {
tickets: this.props.tickets,
type: 'secondary',
loading: this.props.loading,
ticketPath: '/admin/panel/tickets/view-ticket/'
ticketPath: '/admin/panel/tickets/view-ticket/',
page: this.props.page,
pages: this.props.pages,
onPageChange: event => this.retrieveNewTickets(event.target.value)
};
}
retrieveNewTickets(page = this.props.page) {
this.props.dispatch(AdminDataAction.retrieveNewTickets(page));
}
}
export default connect((store) => {
@ -49,6 +57,8 @@ export default connect((store) => {
userId: store.session.userId,
departments: store.session.userDepartments,
tickets: store.adminData.newTickets,
page: store.adminData.newTicketsPage,
pages: store.adminData.newTicketsPages,
loading: !store.adminData.newTicketsLoaded,
error: store.adminData.newTicketsError
};

View File

@ -34,7 +34,6 @@ class DashboardCreateTicketPage extends React.Component {
onCreateTicketSuccess() {
if((this.props.location.pathname !== '/create-ticket')) {
this.props.dispatch(SessionActions.getUserData());
setTimeout(() => {history.push('/dashboard')}, 2000);
} else {
setTimeout(() => {history.push('/check-ticket/' + result.data.ticketNumber + '/' + email)}, 1000);

View File

@ -1,6 +1,8 @@
import React from 'react';
import {connect} from 'react-redux';
import SessionActions from 'actions/session-actions';
import i18n from 'lib-app/i18n';
import Header from 'core-components/header';
@ -15,6 +17,10 @@ class DashboardListTicketsPage extends React.Component {
tickets: []
};
componentDidMount() {
this.retrieveUserData();
}
render() {
return (
<div className="dashboard-ticket-list">
@ -23,6 +29,10 @@ class DashboardListTicketsPage extends React.Component {
</div>
);
}
retrieveUserData() {
this.props.dispatch(SessionActions.getUserData());
}
}

View File

@ -1,59 +1,82 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import store from 'app/store';
import SessionActions from 'actions/session-actions';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import SessionActions from 'actions/session-actions';
import TicketViewer from 'app-components/ticket-viewer';
import Loading from 'core-components/loading';
import Message from 'core-components/message';
class DashboardTicketPage extends React.Component {
static propTypes = {
tickets: React.PropTypes.array
state = {
error: null,
ticket: null,
};
componentDidMount() {
let ticket = this.getTicketData();
if(ticket.unread) {
API.call({
path: '/ticket/seen',
data: {
ticketNumber: ticket.ticketNumber
}
}).then(() => {
this.retrieveUserData();
});
}
this.retrieveTicketData();
}
render() {
let ticketView = i18n('NO_PERMISSION');
if(!_.isEmpty(this.getTicketData())) {
ticketView = <TicketViewer ticket={this.getTicketData()} onChange={this.retrieveUserData.bind(this)}/>;
}
const {ticket, error} = this.state;
return (
<div className="dashboard-ticket-page">
{ticketView}
{(ticket || error) ? this.renderContent() : <Loading className="dashboard-ticket-page__loading" backgrounded/>}
</div>
);
}
getTicketData() {
return _.find(this.props.tickets, {ticketNumber: this.props.params.ticketNumber}) || {};
renderContent() {
const {ticket, error} = this.state;
if(error) {
return (
<Message type="error">
{i18n(error)}
</Message>
);
} else {
return (
<TicketViewer ticket={ticket} onChange={this.retrieveTicketData.bind(this)}/>
);
}
}
retrieveTicketData() {
API.call({
path: '/ticket/get',
data: {
ticketNumber: this.props.params.ticketNumber,
}
})
.then(result => {
const ticket = result.data
this.setState({ticket, error: null})
if(ticket.unread) {
API.call({
path: '/ticket/seen',
data: {
ticketNumber: ticket.ticketNumber
}
}).then(() => {
this.retrieveUserData();
});
}
})
.catch(result => this.setState({error: result.message}));
}
retrieveUserData() {
this.props.dispatch(SessionActions.getUserData());
store.dispatch(SessionActions.getUserData());
}
}
export default connect((store) => {
return {
tickets: store.session.userTickets
};
})(DashboardTicketPage);
export default DashboardTicketPage;

View File

@ -1,3 +1,7 @@
.dashboard-ticket-page {
padding: 0 10px;
}
&__loading {
min-height: 300px;
}
}

View File

@ -45,4 +45,4 @@
padding-bottom: 10px;
}
}
}
}

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Chave Pública do Recaptcha',
'RECAPTCHA_PRIVATE_KEY': 'Private Key do Recaptcha',
'ALLOW_FILE_ATTACHMENTS': 'Permitir anexar arquivos',
'MAX_SIZE_KB': 'Tamanho máximo (KB)',
'MAX_SIZE_MB': 'Tamanho máximo (MB)',
'UPDATE_SETTINGS': 'Atualizar configurações',
'DEFAULT_LANGUAGE': 'Idioma padrão',
'SUPPORTED_LANGUAGES': 'Idiomas suportados',
@ -178,6 +178,7 @@ export default {
'HOME': 'Inicio',
'TICKET_NUMBER': 'Número do chamado',
'NEXT': 'Próximo',
'SUBJECT': 'Sujeito',
'SEND_EMAIL_ON_NEW_TICKET': 'Enviar email para cada novo ticket',
'STAFF_UPDATED': 'Membro da equipe atualizado',
'UPDATE': 'Atualizar',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'privado',
'ENABLE_USER': 'Ativar usuário',
'DISABLE_USER': 'Desativar usuário',
'SHOW_CLOSED_TICKETS': 'Mostrar ingressos fechados',
'IMAGE_HEADER_URL': 'URL do cabeçalho da imagem',
'CHART_CREATE_TICKET': 'Chamados criados',
'CHART_CLOSE': 'Chamados fechados',
@ -334,6 +337,7 @@ export default {
'ERRORS_FOUND': 'Erros encontrados',
'ERROR_IMAGE_SIZE': 'Nenhuma imagem pode ter um tamanho maior que {size} MB',
'USER_DISABLED': 'Esta conta está desativada.',
'INVALID_SYNTAX': 'Sintaxe inválida.',
//MESSAGES
'SIGNUP_SUCCESS': 'Você se registrou com sucesso em nosso sistema de suporte.',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha公鑰',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha私人鑰匙',
'ALLOW_FILE_ATTACHMENTS': '允許文件附件',
'MAX_SIZE_KB': '最大尺寸KB',
'MAX_SIZE_MB': '最大尺寸KB',
'UPDATE_SETTINGS': '更新設置',
'DEFAULT_LANGUAGE': '默認語言',
'SUPPORTED_LANGUAGES': '支持的語言',
@ -178,6 +178,7 @@ export default {
'HOME': '家',
'TICKET_NUMBER': '票號',
'NEXT': '下一個',
'SUBJECT': '学科',
'SEND_EMAIL_ON_NEW_TICKET': '电子邮件为每个新票',
'STAFF_UPDATED': '工作人员已更新',
'UPDATE': '更新',
@ -188,6 +189,8 @@ export default {
'PRIVATE': '私人的',
'ENABLE_USER': '启用用户',
'DISABLE_USER': '禁用用户',
'SHOW_CLOSED_TICKETS': '显示已关闭的门票',
'IMAGE_HEADER_URL': '图片标题网址',
'CHART_CREATE_TICKET': '已創建門票',
'CHART_CLOSE': '門票已關閉',
@ -334,6 +337,7 @@ export default {
'ERRORS_FOUND': '發現錯誤',
'ERROR_IMAGE_SIZE': '没有图像的大小可以超过{size}MB',
'USER_DISABLED': '此帐户已被停用。',
'INVALID_SYNTAX': '无效的语法。',
//MESSAGES
'SIGNUP_SUCCESS': '您已在我們的支持系統中成功註冊',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha - Öffentlicher Schlüssel',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha - Privater Schlüssel',
'ALLOW_FILE_ATTACHMENTS': 'Dateianlagen zulassen',
'MAX_SIZE_KB': 'Max. Größe (KB)',
'MAX_SIZE_MB': 'Max. Größe (MB)',
'UPDATE_SETTINGS': 'Einstellungen aktualisieren',
'DEFAULT_LANGUAGE': 'Standardsprache',
'SUPPORTED_LANGUAGES': 'Unterstützte Sprachen',
@ -178,6 +178,7 @@ export default {
'HOME': 'Home',
'TICKET_NUMBER': 'Ticketnummer',
'NEXT': 'Nächstes',
'SUBJECT': 'Gegenstand',
'SEND_EMAIL_ON_NEW_TICKET': 'E-Mail für jedes neues Ticket',
'STAFF_UPDATED': 'Mitarbeiter wurde aktualisiert',
'UPDATE': 'Aktualisierung',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'Privatgelände',
'ENABLE_USER': 'Benutzer aktivieren',
'DISABLE_USER': 'Benutzer deaktivieren',
'SHOW_CLOSED_TICKETS': 'Geschlossene Tickets anzeigen',
'IMAGE_HEADER_URL': 'URL des Image-Headers',
'CHART_CREATE_TICKET': 'Tickets erstellt',
'CHART_CLOSE': 'Tickets geschlossen',
@ -334,6 +337,7 @@ export default {
'ERRORS_FOUND': 'Fehler gefunden!',
'ERROR_IMAGE_SIZE': 'Kein Bild darf größer als {size} MB sein',
'USER_DISABLED': 'Dieser Account ist deaktiviert.',
'INVALID_SYNTAX': 'Ungültiger Satzbau.',
//MESSAGES
'SIGNUP_SUCCESS': 'Sie haben sich erfolgreich in unserem Support-System registriert.',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha Public Key',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha Private Key',
'ALLOW_FILE_ATTACHMENTS': 'Allow file attachments',
'MAX_SIZE_KB': 'Max Size (KB)',
'MAX_SIZE_MB': 'Max Size (MB)',
'UPDATE_SETTINGS': 'Update settings',
'DEFAULT_LANGUAGE': 'Default Language',
'SUPPORTED_LANGUAGES': 'Supported Languages',
@ -178,6 +178,7 @@ export default {
'HOME': 'Home',
'TICKET_NUMBER': 'Ticket number',
'NEXT': 'Next',
'SUBJECT': 'Subject',
'SEND_EMAIL_ON_NEW_TICKET': 'Send email on new ticket',
'STAFF_UPDATED': 'Staff member has been updated',
'UPDATE': 'Update',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'Private',
'ENABLE_USER': 'Enable User',
'DISABLE_USER': 'Disable User',
'SHOW_CLOSED_TICKETS': 'Show Closed Tickets',
'IMAGE_HEADER_URL': 'Image header URL',
'CHART_CREATE_TICKET': 'Tickets created',
'CHART_CLOSE': 'Tickets closed',
@ -335,6 +338,7 @@ export default {
'ERRORS_FOUND': 'Errors found',
'ERROR_IMAGE_SIZE': 'No image can have a size greater than {size} MB',
'USER_DISABLED': 'This account is disabled.',
'INVALID_SYNTAX': 'Invalid syntax.',
//MESSAGES
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha clave pública',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha clave privada',
'ALLOW_FILE_ATTACHMENTS': 'Permitir archivos adjuntos',
'MAX_SIZE_KB': 'Tamaño máximo (KB)',
'MAX_SIZE_MB': 'Tamaño máximo (MB)',
'UPDATE_SETTINGS': 'Actualizar Configuraciones',
'DEFAULT_LANGUAGE': 'Idioma predeterminado',
'SUPPORTED_LANGUAGES': 'Idiomas Soportados',
@ -178,6 +178,7 @@ export default {
'HOME': 'Inicio',
'TICKET_NUMBER': 'Número de Ticket',
'NEXT': 'Siguiente',
'SUBJECT': 'Asunto',
'SEND_EMAIL_ON_NEW_TICKET': 'Enviar email por cada nuevo ticket',
'STAFF_UPDATED': 'Miembro de Staff actualizado',
'UPDATE': 'Actualizar',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'privado',
'ENABLE_USER': 'Habilitar usuario',
'DISABLE_USER': 'Deshabilitar usuario',
'SHOW_CLOSED_TICKETS': 'Mostrar Tickets Cerrados',
'IMAGE_HEADER_URL': 'URL del encabezado de la imagen',
'CHART_CREATE_TICKET': 'Tickets creados',
'CHART_CLOSE': 'Tickets cerrados',
@ -332,6 +335,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Email o numero de ticket inválido',
'INVALID_FILE': 'Archivo inválido',
'ERRORS_FOUND': 'Se encontraron errores',
'USER_DISABLED': 'Esta cuenta está deshabilitada.',
'INVALID_SYNTAX': 'Sintaxis inválida.',
//MESSAGES
'SIGNUP_SUCCESS': 'Se ha registrado con éxito en nuestro sistema de soporte.',
@ -358,7 +363,6 @@ export default {
'SUCCESS_IMPORTING_CSV_DESCRIPTION': 'El archivo CSV se ha importado correctamente',
'SUCCESS_DELETING_ALL_USERS': 'Los usuarios se han eliminado correctamente',
'ERROR_IMAGE_SIZE': 'Ninguna imagen puede tener un tamaño superior a {size} MB',
'USER_DISABLED': 'Esta cuenta está deshabilitada.',
'LAST_7_DAYS': 'Últimos 7 dias',
'LAST_30_DAYS': 'Últimos 30 dias',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Clé publique Recaptcha',
'RECAPTCHA_PRIVATE_KEY': 'Clé privée Recaptcha',
'ALLOW_FILE_ATTACHMENTS': 'Autoriser les pièces jointes',
'MAX_SIZE_KB': 'Taille Maximale (Ko)',
'MAX_SIZE_MB': 'Taille Maximale (Ko)',
'UPDATE_SETTINGS': 'Mettre à jour les paramètres',
'DEFAULT_LANGUAGE': 'Langue par défaut',
'SUPPORTED_LANGUAGES': 'Langues prises en charges',
@ -178,6 +178,7 @@ export default {
'HOME': 'Accueil',
'TICKET_NUMBER': 'Numéro de ticket',
'NEXT': 'Suivant',
'SUBJECT': 'Assujettir',
'SEND_EMAIL_ON_NEW_TICKET': 'Envoyer un e-mail pour chaque nouveau ticket',
'STAFF_UPDATED': 'Le membre du personnel a été mis à jour',
'UPDATE': 'Mettre à jour',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'privé',
'ENABLE_USER': 'Activer l\'utilisateur',
'DISABLE_USER': 'Désactiver l\'utilisateur',
'SHOW_CLOSED_TICKETS': 'Afficher les billets fermés',
'IMAGE_HEADER_URL': 'URL de l\'en-tête de l\'image',
'CHART_CREATE_TICKET': 'Tickets créés',
'CHART_CLOSE': 'Tickets fermés',
@ -334,6 +337,7 @@ export default {
'ERRORS_FOUND': 'Des erreurs sont survenues',
'ERROR_IMAGE_SIZE': 'Aucune image ne peut avoir une taille supérieure à {size} MB',
'USER_DISABLED': 'Ce compte est désactivé.',
'INVALID_SYNTAX': 'Syntaxe invalide.',
//MESSAGES
'SIGNUP_SUCCESS': 'Vous êtes inscrit avec succès dans notre système de support.',

View File

@ -145,7 +145,7 @@
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha Δημοσίου Κλειδιού',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha Ιδιωτικού Κλειδιού',
'ALLOW_FILE_ATTACHMENTS': 'Επιτρέπονται Συνημμένα Αρχεία',
'MAX_SIZE_KB': 'Μέγιστο Μέγεθος (KB)',
'MAX_SIZE_MB': 'Μέγιστο Μέγεθος (MB)',
'UPDATE_SETTINGS': 'Ενημέρωση Ρυθμίσεων',
'DEFAULT_LANGUAGE': 'Προεπιλεγμένες Γλώσσες',
'SUPPORTED_LANGUAGES': 'Υποστηριζόμενες Γλώσσες',
@ -178,6 +178,7 @@
'HOME': 'Σπίτι',
'TICKET_NUMBER': 'Αριθμός εισιτηρίου',
'NEXT': 'Επόμενο',
'SUBJECT': 'Θέμα',
'SEND_EMAIL_ON_NEW_TICKET': 'Στείλτε μήνυμα ηλκετορνικού ταχυδρομείου στο νέο εισιτήριο',
'STAFF_UPDATED': 'Το μέλος προσωπικού έχει ενημερωθεί',
'UPDATE': 'Ενημέρωση',
@ -188,6 +189,8 @@
'PRIVATE': 'ιδιωτικός',
'ENABLE_USER': 'Ενεργοποίηση χρήστη',
'DISABLE_USER': 'Απενεργοποίηση χρήστη',
'SHOW_CLOSED_TICKETS': 'Εμφάνιση κλειστών εισιτηρίων',
'IMAGE_HEADER_URL': 'Διεύθυνση URL κεφαλίδας εικόνας',
'CHART_CREATE_TICKET': 'Τα εισιτήρια δημιουργήθηκαν',
'CHART_CLOSE': 'Τα εισιτήρια κλείσανε',
@ -334,6 +337,7 @@
'ERRORS_FOUND': 'Βρέθηκαν Σφάλματα',
'ERROR_IMAGE_SIZE': 'Καμία εικόνα δεν μπορεί να έχει μέγεθος μεγαλύτερο από {size} MB',
'USER_DISABLED': 'Αυτός ο λογαριασμός είναι απενεργοποιημένος.',
'INVALID_SYNTAX': 'Μη έγκυρη σύνταξη.',
//MESSAGES
'SIGNUP_SUCCESS': 'Έχετε εγγραφεί με επιτυχία στο σύστημα υποστήριξης μας.',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha सार्वजनिक कुंजी',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha निजी कुंजी',
'ALLOW_FILE_ATTACHMENTS': 'फ़ाइल अनुलग्नकों की अनुमति दें',
'MAX_SIZE_KB': 'अधिकतम आकार (KB)',
'MAX_SIZE_MB': 'अधिकतम आकार (MB)',
'UPDATE_SETTINGS': 'सेटिंग अपडेट करें',
'DEFAULT_LANGUAGE': 'डिफ़ॉल्ट भाषा',
'SUPPORTED_LANGUAGES': 'समर्थित भाषाएँ',
@ -178,6 +178,7 @@ export default {
'HOME': 'घर',
'TICKET_NUMBER': 'टिकट नंबर',
'NEXT': 'आगामी',
'SUBJECT': 'विषय',
'SEND_EMAIL_ON_NEW_TICKET': 'एक ईमेल भेजने के लिए प्रत्येक नए टिकट',
'STAFF_UPDATED': 'स्टाफ सदस्य को अद्यतन किया गया है',
'UPDATE': 'अद्यतन',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'निजी',
'ENABLE_USER': 'उपयोगकर्ता सक्षम करें',
'DISABLE_USER': 'उपयोगकर्ता को अक्षम करें',
'SHOW_CLOSED_TICKETS': 'बंद टिकट दिखाएं',
'IMAGE_HEADER_URL': 'छवि शीर्षलेख यूआरएल',
'CHART_CREATE_TICKET': 'टिकट बनाया',
'CHART_CLOSE': 'टिकट बंद कर दिया',
@ -334,6 +337,7 @@ export default {
'ERRORS_FOUND': 'त्रुटियां मिलीं',
'ERROR_IMAGE_SIZE': 'कोई छवि {size} एमबी से अधिक आकार नहीं हो सकती है',
'USER_DISABLED': 'यह खाता अक्षम है।',
'INVALID_SYNTAX': 'अवैध वाक्य रचना।',
//MESSAGES
'SIGNUP_SUCCESS': 'आप हमारे समर्थन प्रणाली में सफलतापूर्वक दर्ज कर लिया है।',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha Public Key',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha Private Key',
'ALLOW_FILE_ATTACHMENTS': 'Consenti di allegare file',
'MAX_SIZE_KB': 'Dimensione massima (KB)',
'MAX_SIZE_MB': 'Dimensione massima (MB)',
'UPDATE_SETTINGS': 'Aggiorna modifiche',
'DEFAULT_LANGUAGE': 'Lingua predefinita',
'SUPPORTED_LANGUAGES': 'Lingue supportate',
@ -178,6 +178,7 @@ export default {
'HOME': 'Home',
'TICKET_NUMBER': 'Ticket numero',
'NEXT': 'Prossimo',
'SUBJECT': 'Soggetto',
'SEND_EMAIL_ON_NEW_TICKET': 'Invia e-mail al nuovo ticket',
'STAFF_UPDATED': 'Il membro del personale è stato aggiornato',
'UPDATE': 'Aggiornare',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'privato',
'ENABLE_USER': 'Abilita utente',
'DISABLE_USER': 'Disabilita utente',
'SHOW_CLOSED_TICKETS': 'Mostra biglietti chiusi',
'IMAGE_HEADER_URL': 'URL dell\'intestazione dell\'immagine',
'CHART_CREATE_TICKET': 'Tickets creato',
'CHART_CLOSE': 'Tickets chiuso',
@ -334,6 +337,7 @@ export default {
'ERRORS_FOUND': 'Trovati errori',
'ERROR_IMAGE_SIZE': 'Nessuna immagine può avere una dimensione superiore a {size} MB',
'USER_DISABLED': 'Questo account è disabilitato.',
'INVALID_SYNTAX': 'Sintassi non valida.',
//MESSAGES
'SIGNUP_SUCCESS': 'È stato registrato con successo nel nostro sistema di supporto.',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha公開鍵',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha秘密鍵',
'ALLOW_FILE_ATTACHMENTS': '添付ファイルを許可する',
'MAX_SIZE_KB': '最大サイズKB',
'MAX_SIZE_MB': '最大サイズKB',
'UPDATE_SETTINGS': '設定を更新する',
'DEFAULT_LANGUAGE': '既定の言語',
'SUPPORTED_LANGUAGES': 'サポートされている言語',
@ -178,6 +178,7 @@ export default {
'HOME': 'ホーム',
'TICKET_NUMBER': 'チケット番号',
'NEXT': '次',
'SUBJECT': '件名',
'SEND_EMAIL_ON_NEW_TICKET': 'メールを送信毎に新しいチケット',
'STAFF_UPDATED': 'スタッフメンバーが更新されました',
'UPDATE': '更新',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'プライベート',
'ENABLE_USER': 'ユーザーを有効にする',
'DISABLE_USER': 'ユーザーを無効にする',
'SHOW_CLOSED_TICKETS': 'クローズドチケットを表示する',
'IMAGE_HEADER_URL': '画像のヘッダーURL',
'CHART_CREATE_TICKET': '作成されたチケット',
'CHART_CLOSE': 'チケットが閉じられました',
@ -334,6 +337,7 @@ export default {
'ERRORS_FOUND': 'エラーが見つかりました',
'ERROR_IMAGE_SIZE': 'イメージのサイズが{size} MBを超えることはできません',
'USER_DISABLED': 'このアカウントは無効です。',
'INVALID_SYNTAX': '無効な構文。',
//MESSAGES
'SIGNUP_SUCCESS': 'あなたは私たちのサポートシステムに正常に登録しました。',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha Publieke Sleutel',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha Prive Sleutel',
'ALLOW_FILE_ATTACHMENTS': 'Sta bijlage(s) toe',
'MAX_SIZE_KB': 'Max grootte (KB)',
'MAX_SIZE_MB': 'Max grootte (MB)',
'UPDATE_SETTINGS': 'Update instellingen',
'DEFAULT_LANGUAGE': 'Standaard Taal',
'SUPPORTED_LANGUAGES': 'Talen die zijn ondersteund',
@ -178,6 +178,7 @@ export default {
'HOME': 'Home',
'TICKET_NUMBER': 'Incidentnummer',
'NEXT': 'Volgende',
'SUBJECT': 'Bedrijf',
'SEND_EMAIL_ON_NEW_TICKET': 'Stuur e-mail bij nieuw incident',
'STAFF_UPDATED': 'Medewerker is gewijzigd',
'UPDATE': 'Update',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'privaat',
'ENABLE_USER': 'Schakel gebruiker in',
'DISABLE_USER': 'Gebruiker uitschakelen',
'SHOW_CLOSED_TICKETS': 'Toon gesloten tickets',
'IMAGE_HEADER_URL': 'Image header URL',
'CHART_CREATE_TICKET': 'Aangemaakte incidenten',
'CHART_CLOSE': 'Gesloten incidenten',
@ -334,6 +337,7 @@ export default {
'ERRORS_FOUND': 'Er is een fout opgetreden',
'ERROR_IMAGE_SIZE': 'Geen enkele afbeelding kan groter zijn dan {size} MB',
'USER_DISABLED': 'Dit account is uitgeschakeld.',
'INVALID_SYNTAX': 'Ongeldige syntaxis.',
//MESSAGES
'SIGNUP_SUCCESS': 'U hebt zich succesvol geregistreerd in ons ondersteuningssysteem.',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha Chave Pública',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha Private Key',
'ALLOW_FILE_ATTACHMENTS': 'Permitir anexos de arquivo',
'MAX_SIZE_KB': 'Tamanho máximo (KB)',
'MAX_SIZE_MB': 'Tamanho máximo (MB)',
'UPDATE_SETTINGS': 'Atualizar configurações',
'DEFAULT_LANGUAGE': 'Idioma padrão',
'SUPPORTED_LANGUAGES': 'Idiomas suportados',
@ -178,6 +178,7 @@ export default {
'HOME': 'Casa',
'TICKET_NUMBER': 'Número do bilhete',
'NEXT': 'Próximo',
'SUBJECT': 'Sujeito',
'SEND_EMAIL_ON_NEW_TICKET': 'Enviar email para cada novo ticket',
'STAFF_UPDATED': 'Membro da equipe foi atualizado',
'UPDATE': 'Actualizar',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'privado',
'ENABLE_USER': 'Ativar usuário',
'DISABLE_USER': 'Desativar usuário',
'SHOW_CLOSED_TICKETS': 'Mostrar ingressos fechados',
'IMAGE_HEADER_URL': 'URL do cabeçalho da imagem',
'CHART_CREATE_TICKET': 'Ingressos criados',
'CHART_CLOSE': 'Ingressos fechados',
@ -334,6 +337,7 @@ export default {
'ERRORS_FOUND': 'Erros encontrados',
'ERROR_IMAGE_SIZE': 'Nenhuma imagem pode ter um tamanho maior que {size} MB',
'USER_DISABLED': 'Esta conta está desativada.',
'INVALID_SYNTAX': 'Sintaxe inválida.',
//MESSAGES
'SIGNUP_SUCCESS': 'Você se registrou com sucesso em nosso sistema de suporte.',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Recaptcha Открытый ключ',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha Секретный ключ',
'ALLOW_FILE_ATTACHMENTS': 'Разрешить файловые вложения',
'MAX_SIZE_KB': 'Максимальный размер (КБ)',
'MAX_SIZE_MB': 'Максимальный размер (КБ)',
'UPDATE_SETTINGS': 'Обновить настройки',
'DEFAULT_LANGUAGE': 'Язык по умолчанию',
'SUPPORTED_LANGUAGES': 'Поддерживаемые Языки',
@ -178,6 +178,7 @@ export default {
'HOME': 'Главная',
'TICKET_NUMBER': 'Номер билета',
'NEXT': 'следующий',
'SUBJECT': 'Предмет',
'SEND_EMAIL_ON_NEW_TICKET': 'Отправить письмо на новый билет',
'STAFF_UPDATED': 'Сотрудник обновлен',
'UPDATE': 'Обновить',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'частный',
'ENABLE_USER': 'Включить пользователя',
'DISABLE_USER': 'Отключить пользователя',
'SHOW_CLOSED_TICKETS': 'Показать закрытые билеты',
'IMAGE_HEADER_URL': 'URL заголовка изображения',
'CHART_CREATE_TICKET': 'Билеты создано',
'CHART_CLOSE': ' Билеты закрыты',
@ -335,6 +338,7 @@ export default {
'ERRORS_FOUND': 'Ошибки найдены',
'ERROR_IMAGE_SIZE': 'Изображение не может иметь размер больше {size} МБ',
'USER_DISABLED': 'Эта учетная запись отключена.',
'INVALID_SYNTAX': 'Недопустимый синтаксис.',
//MESSAGES
'SIGNUP_SUCCESS': 'Вы успешно зарегистрировались в нашей системе поддержки.',

View File

@ -145,7 +145,7 @@ export default {
'RECAPTCHA_PUBLIC_KEY': 'Hızlı Erişim Anahtarı',
'RECAPTCHA_PRIVATE_KEY': 'Recaptcha Özel Anahtar',
'ALLOW_FILE_ATTACHMENTS': 'Dosya eklerine izin ver',
'MAX_SIZE_KB': 'Maksimum Boyut (KB)',
'MAX_SIZE_MB': 'Maksimum Boyut (MB)',
'UPDATE_SETTINGS': 'Ayarları güncelle',
'DEFAULT_LANGUAGE': 'Varsayılan dil',
'SUPPORTED_LANGUAGES': 'Desteklenen Diller',
@ -178,6 +178,7 @@ export default {
'HOME': 'Ev',
'TICKET_NUMBER': 'Bilet numarası',
'NEXT': 'Sonraki',
'SUBJECT': 'konu',
'SEND_EMAIL_ON_NEW_TICKET': 'Yeni biletle e-posta gönder',
'STAFF_UPDATED': 'Çalışanlar güncellendi',
'UPDATE': 'Güncelleştirme',
@ -188,6 +189,8 @@ export default {
'PRIVATE': 'gizli',
'ENABLE_USER': 'Kullanıcıyı Etkinleştir',
'DISABLE_USER': 'Kullanıcıyı Devre Dışı Bırak',
'SHOW_CLOSED_TICKETS': 'Kapalı Biletleri Göster',
'IMAGE_HEADER_URL': 'Resim başlığı URL\'si',
'CHART_CREATE_TICKET': 'Biletler oluşturuldu',
'CHART_CLOSE': 'Biletler kapandı',
@ -335,6 +338,7 @@ export default {
'ERRORS_FOUND': 'Hatalar bulundu',
'ERROR_IMAGE_SIZE': 'Hiçbir resmin boyutu {size} MB\'den büyük olabilir',
'USER_DISABLED': 'Bu hesap devre dışı.',
'INVALID_SYNTAX': 'Geçersiz sözdizimi.',
//MESSAGES
'SIGNUP_SUCCESS': 'Destek sistemimize başarılı bir şekilde kayıt oldunuz.',

View File

@ -0,0 +1,53 @@
const PARSING_TEXT = 0;
const PARSING_MENTION = 1;
class MentionsParser {
parse(text) {
let parsingLink = false;
let parsingType = PARSING_TEXT;
let parsingSegment = '';
let ans = '';
for(let index = 0; index < text.length; ++index){
let character = text[index];
if(character == '#'){
ans += this.compileSegment(parsingSegment, parsingType);
parsingLink = true;
parsingType = PARSING_MENTION;
parsingSegment = '';
} else if(!this.isDigit(character) && parsingLink){
ans += this.compileSegment(parsingSegment, parsingType);
parsingLink = false;
parsingType = PARSING_TEXT;
parsingSegment = character;
} else {
parsingSegment += character;
}
}
ans += this.compileSegment(parsingSegment, parsingType);
return ans;
}
isDigit(string){
return /[0-9]/.test(string);
}
compileSegment(segment, parsingType){
switch(parsingType){
case PARSING_TEXT:
return segment;
case PARSING_MENTION:
return '<a href=' + root + '/admin/panel/tickets/view-ticket/' + segment + '>#' + segment + '</a>';
default:
return '';
}
}
};
export default new MentionsParser;

View File

@ -11,14 +11,20 @@ class AdminDataReducer extends Reducer {
customResponsesLoaded: false,
myTickets: [],
myTicketsPage: 1,
myTicketsPages: 1,
myTicketsLoaded: false,
myTicketsError: false,
newTickets: [],
newTicketsPage: 1,
newTicketsPages: 1,
newTicketsLoaded: false,
newTicketsError: false,
allTickets: [],
allTicketsPage: 1,
allTicketsPages: 1,
allTicketsLoaded: false,
allTicketsError: false,
@ -61,7 +67,9 @@ class AdminDataReducer extends Reducer {
onMyTicketsRetrieved(state, payload) {
return _.extend({}, state, {
myTickets: payload.data,
myTickets: payload.data.tickets,
myTicketsPage: payload.data.page * 1,
myTicketsPages: payload.data.pages * 1,
myTicketsLoaded: true
});
}
@ -82,7 +90,9 @@ class AdminDataReducer extends Reducer {
onNewTicketsRetrieved(state, payload) {
return _.extend({}, state, {
newTickets: payload.data,
newTickets: payload.data.tickets,
newTicketsPage: payload.data.page * 1,
newTicketsPages: payload.data.pages * 1,
newTicketsLoaded: true
});
}
@ -104,7 +114,8 @@ class AdminDataReducer extends Reducer {
onAllTicketsRetrieved(state, payload) {
return _.extend({}, state, {
allTickets: payload.data.tickets,
allTicketsPages: payload.data.pages,
allTicketsPage: payload.data.page * 1,
allTicketsPages: payload.data.pages * 1,
allTicketsLoaded: true
});
}

View File

@ -11,5 +11,8 @@
},
"require-dev": {
"phpunit/phpunit": "^5.7"
},
"autoload":{
"classmap": ["libs/", "models/", "controllers/", "data/"]
}
}

View File

@ -1,12 +1,4 @@
<?php
include 'article/add-topic.php';
include 'article/edit-topic.php';
include 'article/delete-topic.php';
include 'article/add.php';
include 'article/edit.php';
include 'article/delete.php';
include 'article/get-all.php';
$articleControllers = new ControllerGroup();
$articleControllers->setGroupPath('/article');

View File

@ -1,17 +1,4 @@
<?php
require_once 'staff/get.php';
require_once 'staff/assign-ticket.php';
require_once 'staff/un-assign-ticket.php';
require_once 'staff/get-tickets.php';
require_once 'staff/get-new-tickets.php';
require_once 'staff/get-all-tickets.php';
require_once 'staff/search-tickets.php';
require_once 'staff/add.php';
require_once 'staff/get-all.php';
require_once 'staff/delete.php';
require_once 'staff/edit.php';
require_once 'staff/last-events.php';
$systemControllerGroup = new ControllerGroup();
$systemControllerGroup->setGroupPath('/staff');

View File

@ -2,18 +2,20 @@
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/get-all-tickets Get all tickets
* @api {post} /staff/get-all-tickets Get all tickets according to search
* @apiVersion 4.3.0
*
* @apiName Get all tickets
*
* @apiGroup Staff
*
* @apiDescription This path retrieves all tickets.
* @apiDescription This path retrieves all tickets according to search and opened/closed filters.
*
* @apiPermission staff1
*
* @apiParam {Number} page The page number.
* @apiParam {String} query Query string to search.
* @apiParam {Boolean} closed Include closed tickets.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PAGE
@ -29,7 +31,7 @@ class GetAllTicketsStaffController extends Controller {
const METHOD = 'POST';
public function validations() {
return[
return [
'permission' => 'staff_1',
'requestData' => [
'page' => [
@ -50,7 +52,7 @@ class GetAllTicketsStaffController extends Controller {
}
Response::respondSuccess([
'tickets' => $this->getTicketList()->toArray(),
'tickets' => $this->getTicketList()->toArray(true),
'pages' => $this->getTotalPages()
]);
}
@ -58,16 +60,35 @@ class GetAllTicketsStaffController extends Controller {
private function getTicketList() {
$page = Controller::request('page');
$query = $this->getStaffDepartmentsQueryFilter();
$query .= 'ORDER BY id DESC LIMIT 10 OFFSET ' . (($page-1)*10);
$query = $this->getSearchQuery();
$query .= $this->getStaffDepartmentsQueryFilter();
$query .= $this->getClosedFilter();
$query .= "ORDER BY CASE WHEN (title LIKE ?) THEN 1 ELSE 2 END ASC, id DESC LIMIT 10 OFFSET " . (($page-1)*10);
return Ticket::find($query);
return Ticket::find($query, [
Controller::request('query') . '%',
'%' . Controller::request('query') . '%',
Controller::request('query') . '%'
]);
}
private function getSearchQuery() {
$page = Controller::request('page');
$query = " (title LIKE ? OR title LIKE ?) AND ";
return $query;
}
private function getTotalPages() {
$query = $this->getStaffDepartmentsQueryFilter();
$query = $this->getSearchQuery();
$query .= $this->getStaffDepartmentsQueryFilter();
$query .= $this->getClosedFilter();
return ceil(Ticket::count($query) / 10);
return ceil(Ticket::count($query, [
Controller::request('query') . '%',
'%' . Controller::request('query') . '%'
]) / 10);
}
private function getStaffDepartmentsQueryFilter() {
@ -81,4 +102,13 @@ class GetAllTicketsStaffController extends Controller {
return $query;
}
private function getClosedFilter() {
$closed = Controller::request('closed')*1;
if ($closed) {
return '';
} else {
return " AND (closed = '0')";
}
}
}

View File

@ -14,9 +14,15 @@ use Respect\Validation\Validator as DataValidator;
*
* @apiPermission staff1
*
* @apiUse NO_PERMISSION
* @apiParam {Number} page The page number.
*
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data Array of new tickets.
* @apiUse NO_PERMISSION
* @apiUse INVALID_PAGE
*
* @apiSuccess {Object} data Information about a tickets and quantity of pages.
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data.tickets Array of new tickets of the current page.
* @apiSuccess {Number} data.page Number of current page.
* @apiSuccess {Number} data.pages Quantity of pages.
*
*/
@ -27,7 +33,12 @@ class GetNewTicketsStaffController extends Controller {
public function validations() {
return[
'permission' => 'staff_1',
'requestData' => []
'requestData' => [
'page' => [
'validation' => DataValidator::numeric(),
'error' => ERRORS::INVALID_PAGE
]
]
];
}
public function handler() {
@ -37,6 +48,8 @@ class GetNewTicketsStaffController extends Controller {
}
$user = Controller::getLoggedUser();
$page = Controller::request('page');
$query = ' (';
foreach ($user->sharedDepartmentList as $department) {
$query .= 'department_id=' . $department->id . ' OR ';
@ -45,13 +58,22 @@ class GetNewTicketsStaffController extends Controller {
$ownerExists = RedBean::exec('SHOW COLUMNS FROM ticket LIKE \'owner_id\'');
if($ownerExists != 0) {
$query .= 'FALSE) AND owner_id IS NULL';
$query .= 'FALSE) AND closed = 0 AND owner_id IS NULL';
} else {
$query .= 'FALSE)';
$query .= 'FALSE) AND closed = 0';
}
$countTotal = Ticket::count($query);
$query .= ' ORDER BY unread_staff DESC';
$query .= ' LIMIT 10 OFFSET ' . ($page-1)*10;
$ticketList = Ticket::find($query);
Response::respondSuccess($ticketList->toArray());
Response::respondSuccess([
'tickets' => $ticketList->toArray(true),
'page' => $page,
'pages' => ceil($countTotal / 10)
]);
}
}

View File

@ -13,9 +13,16 @@ use Respect\Validation\Validator as DataValidator;
*
* @apiPermission staff1
*
* @apiParam {Number} page The page number.
* @apiParam {bool} closed Include closed tickets in the response.
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data Array of tickets assigned to the staff
* @apiUse INVALID_PAGE
*
* @apiSuccess {Object} data Information about a tickets and quantity of pages.
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data.tickets Array of tickets assigned to the staff of the current page.
* @apiSuccess {Number} data.page Number of current page.
* @apiSuccess {Number} data.pages Quantity of pages.
*
*/
@ -26,12 +33,33 @@ class GetTicketStaffController extends Controller {
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => []
'requestData' => [
'page' => [
'validation' => DataValidator::numeric(),
'error' => ERRORS::INVALID_PAGE
]
]
];
}
public function handler() {
$user = Controller::getLoggedUser();
Response::respondSuccess($user->sharedTicketList->toArray());
$closed = Controller::request('closed');
$page = Controller::request('page');
$offset = ($page-1)*10;
if ($closed) {
$tickets = $user->withCondition(' TRUE LIMIT 10 OFFSET ?', [$offset])->sharedTicketList->toArray(true);
$countTotal = $user->countShared('ticket');
} else {
$tickets = $user->withCondition(' closed = ? LIMIT 10 OFFSET ?', ['0', $offset])->sharedTicketList->toArray(true);
$countTotal = $user->withCondition(' closed = ?', ['0'])->countShared('ticket');
}
Response::respondSuccess([
'tickets' => $tickets,
'page' => $page,
'pages' => ceil($countTotal / 10)
]);
}
}
}

View File

@ -1,32 +1,4 @@
<?php
require_once 'system/check-requirements.php';
require_once 'system/init-database.php';
require_once 'system/init-settings.php';
require_once 'system/init-admin.php';
require_once 'system/installation-done.php';
require_once 'system/get-settings.php';
require_once 'system/edit-settings.php';
require_once 'system/add-department.php';
require_once 'system/edit-department.php';
require_once 'system/delete-department.php';
require_once 'system/get-logs.php';
require_once 'system/get-mail-templates.php';
require_once 'system/edit-mail-template.php';
require_once 'system/recover-mail-template.php';
require_once 'system/disable-registration.php';
require_once 'system/enable-registration.php';
require_once 'system/disable-user-system.php';
require_once 'system/enable-user-system.php';
require_once 'system/add-api-key.php';
require_once 'system/delete-api-key.php';
require_once 'system/get-api-keys.php';
require_once 'system/get-stats.php';
require_once 'system/delete-all-users.php';
require_once 'system/csv-import.php';
require_once 'system/backup-database.php';
require_once 'system/download.php';
require_once 'system/test-smtp.php';
$systemControllerGroup = new ControllerGroup();
$systemControllerGroup->setGroupPath('/system');
@ -41,7 +13,8 @@ $systemControllerGroup->addController(new AddDepartmentController);
$systemControllerGroup->addController(new EditDepartmentController);
$systemControllerGroup->addController(new DeleteDepartmentController);
$systemControllerGroup->addController(new GetLogsController);
$systemControllerGroup->addController(new GetMailTemplatesController);
$systemControllerGroup->addController(new GetMailTemplateListController);
$systemControllerGroup->addController(new GetMailTemplateController);
$systemControllerGroup->addController(new EditMailTemplateController);
$systemControllerGroup->addController(new RecoverMailTemplateController);
$systemControllerGroup->addController(new DisableRegistrationController);

View File

@ -9,20 +9,24 @@ use Respect\Validation\Validator as DataValidator;
*
* @apiGroup System
*
* @apiDescription This path edit a mail template.
* @apiDescription This path edits a mail template.
*
* @apiPermission staff3
*
* @apiParam {String} templateType The new type of the template.
* @apiParam {String} language The new language of the template.
* @apiParam {String} template The template to edit.
* @apiParam {String} language The language of the template to edit.
* @apiParam {String} subject The new subject of the template.
* @apiParam {String} body The new content of the template.
* @apiParam {String} text1 The first paragraph template.
* @apiParam {String} text2 The second paragraph template.
* @apiParam {String} text3 The third paragraph template.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TEMPLATE
* @apiUse INVALID_LANGUAGE
* @apiUse INVALID_SUBJECT
* @apiUse INVALID_BODY
* @apiUse INVALID_TEXT_1
* @apiUse INVALID_TEXT_2
* @apiUse INVALID_TEXT_3
*
* @apiSuccess {Object} data Empty object
*
@ -32,11 +36,16 @@ class EditMailTemplateController extends Controller {
const PATH = '/edit-mail-template';
const METHOD = 'POST';
private $langauge;
private $templateType;
private $subject;
private $texts;
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'templateType' => [
'template' => [
'validation' => DataValidator::length(4),
'error' => ERRORS::INVALID_TEMPLATE
],
@ -48,28 +57,87 @@ class EditMailTemplateController extends Controller {
'validation' => DataValidator::length(4),
'error' => ERRORS::INVALID_SUBJECT
],
'body' => [
'validation' => DataValidator::length(4),
'error' => ERRORS::INVALID_BODY
]
]
];
}
public function handler() {
$language = Controller::request('language');
$templateType = Controller::request('templateType');
$subject = Controller::request('subject', true);
$body = Controller::request('body');
$this->language = Controller::request('language');
$this->templateType = Controller::request('template');
$this->subject = Controller::request('subject', true);
$this->texts = [
Controller::request('text1'),
Controller::request('text2'),
Controller::request('text3'),
];
$mailTemplate = MailTemplate::findOne(' language = ? AND template = ?', [$this->language, $this->templateType]);
$mailTemplate = MailTemplate::findOne(' language = ? AND type = ?', [$language, $templateType]);
if($mailTemplate->isNull()) {
throw new Exception(ERRORS::INVALID_TEMPLATE);
}
$mailTemplate->subject = $subject;
$mailTemplate->body = $body;
$this->validateReplacements();
$mailTemplate->subject = $this->subject;
$mailTemplate->text1 = $this->texts[0];
$mailTemplate->text2 = $this->texts[1];
$mailTemplate->text3 = $this->texts[2];
$mailTemplate->store();
Response::respondSuccess();
}
public function validateReplacements() {
$originalText = MailTexts::getTexts()[$this->language][$this->templateType];
if(!$this->includes(
$this->getReplacementStrings($originalText[1]),
$this->getReplacementStrings($this->texts[0])
)) {
throw new Exception(ERRORS::INVALID_TEXT_1);
}
if(!$this->includes(
$this->getReplacementStrings($originalText[2]),
$this->getReplacementStrings($this->texts[1])
)) {
throw new Exception(ERRORS::INVALID_TEXT_2);
}
if(!$this->includes(
$this->getReplacementStrings($originalText[3]),
$this->getReplacementStrings($this->texts[2])
)) {
throw new Exception(ERRORS::INVALID_TEXT_3);
}
}
public function includes($array1, $array2) {
foreach($array1 as $item) {
if(!in_array($item, $array2)) return false;
}
return true;
}
public function getReplacementStrings($string) {
$replacements = [];
for($i=0; $i<strlen($string)-1; $i++) {
if($string[$i] == '{' && $string[$i+1] == '{') {
$replacement = "";
$i += 2;
for(; $i<strlen($string)-1;$i++) {
if($string[$i] == '}' && $string[$i+1] == '}') break;
$replacement .= $string[$i];
}
$replacements[] = $replacement;
}
}
return $replacements;
}
}

View File

@ -49,7 +49,8 @@ class EditSettingsController extends Controller {
'allow-attachments',
'max-size',
'title',
'url'
'url',
'mail-template-header-image'
];
foreach($settings as $setting) {

View File

@ -39,9 +39,19 @@ class GetLogsController extends Controller {
}
public function handler() {
$this->deleteLastLogs();
$page = Controller::request('page');
$logList = Log::find('ORDER BY id desc LIMIT ? OFFSET ?', [10, 10*($page-1)]);
Response::respondSuccess($logList->toArray());
}
}
public function deleteLastLogs() {
$removeOlderThanDays = 31;
$oldDate = floor(Date::getPreviousDate($removeOlderThanDays) / 10000);
try {
RedBean::exec("DELETE FROM log WHERE date < $oldDate");
} catch(Exception $e) {}
}
}

View File

@ -2,25 +2,25 @@
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-mail-templates Get mail templates
* @api {post} /system/get-mail-template-list Get mail template
* @apiVersion 4.3.0
*
* @apiName Get mail templates
* @apiName Get mail template list
*
* @apiGroup System
*
* @apiDescription This path retrieves the all the mail templates.
* @apiDescription This path retrieves the list of mail templates
*
* @apiPermission staff3
*
* @apiUse NO_PERMISSION
*
*
* @apiSuccess {[MailTemplate](#api-Data_Structures-ObjectMailtemplate)[]} data Array of mail templates
*
*/
class GetMailTemplatesController extends Controller {
const PATH = '/get-mail-templates';
class GetMailTemplateListController extends Controller {
const PATH = '/get-mail-template-list';
const METHOD = 'POST';
public function validations() {
@ -31,7 +31,6 @@ class GetMailTemplatesController extends Controller {
}
public function handler() {
Response::respondSuccess(MailTemplate::getAll()->toArray());
Response::respondSuccess(array_keys(MailTemplate::getFilePaths()));
}
}
}

View File

@ -0,0 +1,57 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-mail-template Get mail template
* @apiVersion 4.3.0
*
* @apiName Get mail template
*
* @apiGroup System
*
* @apiDescription This path retrieves the data of one of the mail templates.
*
* @apiPermission staff3
*
* @apiParam {String} template The type of template to retrieve.
* @apiParam {String} language The language of the template to retrieve.
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[MailTemplate](#api-Data_Structures-ObjectMailtemplate)} data Data of the mail template
*
*/
class GetMailTemplateController extends Controller {
const PATH = '/get-mail-template';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'template' => [
'validation' => DataValidator::length(4),
'error' => ERRORS::INVALID_TEMPLATE
],
'language' => [
'validation' => DataValidator::length(2, 2),
'error' => ERRORS::INVALID_LANGUAGE
],
]
];
}
public function handler() {
$type = Controller::request('template');
$language = Controller::request('language');
$mailTemplate = MailTemplate::findOne(' language = ? AND template = ?', [$language, $type]);
if($mailTemplate->isNull()) {
throw new Exception(ERRORS::INVALID_TEMPLATE);
}
Response::respondSuccess($mailTemplate->toArray());
}
}

View File

@ -53,7 +53,8 @@ class GetSettingsController extends Controller {
'departments' => Department::getAllDepartmentNames(),
'supportedLanguages' => Language::getSupportedLanguages(),
'allowedLanguages' => Language::getAllowedLanguages(),
'session-prefix' => Setting::getSetting('session-prefix')
'session-prefix' => Setting::getSetting('session-prefix')->getValue(),
'mail-template-header-image' => Setting::getSetting('mail-template-header-image')->getValue()
];
} else {
$settingsList = [
@ -70,7 +71,7 @@ class GetSettingsController extends Controller {
'supportedLanguages' => Language::getSupportedLanguages(),
'allowedLanguages' => Language::getAllowedLanguages(),
'user-system-enabled' => intval(Setting::getSetting('user-system-enabled')->getValue()),
'session-prefix' => Setting::getSetting('session-prefix')
'session-prefix' => Setting::getSetting('session-prefix')->getValue()
];
}
}

View File

@ -1,5 +1,4 @@
<?php
//include '../../config.php';
use RedBeanPHP\Facade as RedBean;
/**

View File

@ -85,25 +85,25 @@ class InitSettingsController extends Controller {
'last-stat-day' => date('YmdHi', strtotime(' -12 day ')),
'ticket-gap' => Hashing::generateRandomPrime(100000, 999999),
'ticket-first-number' => Hashing::generateRandomNumber(100000, 999999),
'file-gap' => Hashing::generateRandomPrime(100000, 999999),
'file-first-number' => Hashing::generateRandomNumber(100000, 999999),
'file-quantity' => 0,
'session-prefix' => 'opensupports-'.Hashing::generateRandomToken().'_'
'session-prefix' => 'opensupports-'.Hashing::generateRandomToken().'_',
'mail-template-header-image' => 'http://opensupports.com/logo.png'
]);
}
private function storeMailTemplates() {
$mails = InitialMails::retrieve();
$mailLanguages = MailTexts::getTexts();
foreach ($mails as $mailType => $mailLanguages) {
foreach ($mailLanguages as $mailLanguage => $mailContent) {
foreach ($mailLanguages as $language => $mailTemplate) {
foreach ($mailTemplate as $template => $texts) {
$mailTemplate = new MailTemplate();
$mailTemplate->setProperties([
'type' => $mailType,
'language' => $mailLanguage,
'subject' => $mailContent['subject'],
'body' => $mailContent['body']
'template' => $template,
'language' => $language,
'subject' => $texts[0],
'text1' => array_key_exists(1, $texts) ? $texts[1] : '',
'text2' => array_key_exists(2, $texts) ? $texts[2] : '',
'text3' => array_key_exists(3, $texts) ? $texts[3] : '',
]);
$mailTemplate->store();

View File

@ -13,7 +13,7 @@ use Respect\Validation\Validator as DataValidator;
*
* @apiPermission staff3
*
* @apiParam {String} templateType Type of the template.
* @apiParam {String} template Type of the template.
* @apiParam {String} language Lenguage of the template.
*
* @apiUse NO_PERMISSION
@ -32,7 +32,7 @@ class RecoverMailTemplateController extends Controller {
return [
'permission' => 'staff_3',
'requestData' => [
'templateType' => [
'template' => [
'validation' => DataValidator::length(4),
'error' => ERRORS::INVALID_TEMPLATE
],
@ -45,23 +45,24 @@ class RecoverMailTemplateController extends Controller {
}
public function handler() {
$type = Controller::request('templateType');
$templateType = Controller::request('template');
$language = Controller::request('language');
$mailTemplate = MailTemplate::findOne(' language = ? AND type = ?', [$language, $type]);
$mailTemplate = MailTemplate::findOne(' language = ? AND template = ?', [$language, $templateType]);
if($mailTemplate->isNull()) {
Response::respondError(ERRORS::INVALID_TEMPLATE);
return;
throw new Exception(ERRORS::INVALID_TEMPLATE);
}
$defaultTemplates = InitialMails::retrieve();
$mailTemplate->body = $defaultTemplates[$type][$language]['body'] ;
$mailTemplate->subject = $defaultTemplates[$type][$language]['subject'] ;
$mailTexts = MailTexts::getTexts()[$language][$templateType];
$mailTemplate->subject = $mailTexts[0];
$mailTemplate->text1 = (array_key_exists(1, $mailTexts)) ? $mailTexts[1] : '';
$mailTemplate->text2 = (array_key_exists(2, $mailTexts)) ? $mailTexts[2] : '';
$mailTemplate->text3 = (array_key_exists(3, $mailTexts)) ? $mailTexts[3] : '';
$mailTemplate->store();
Response::respondSuccess();
Response::respondSuccess();
}
}
}

View File

@ -1,19 +1,4 @@
<?php
include 'ticket/create.php';
include 'ticket/comment.php';
include 'ticket/get.php';
include 'ticket/check.php';
include 'ticket/add-custom-response.php';
include 'ticket/delete-custom-response.php';
include 'ticket/edit-custom-response.php';
include 'ticket/get-custom-responses.php';
include 'ticket/change-department.php';
include 'ticket/close.php';
include 'ticket/re-open.php';
include 'ticket/change-priority.php';
include 'ticket/seen.php';
include 'ticket/delete.php';
$ticketControllers = new ControllerGroup();
$ticketControllers->setGroupPath('/ticket');

View File

@ -1,23 +1,4 @@
<?php
include 'user/login.php';
include 'user/signup.php';
include 'user/logout.php';
include 'user/check-session.php';
include 'user/recover-password.php';
include 'user/send-recover-password.php';
include 'user/edit-password.php';
include 'user/edit-email.php';
include 'user/get.php';
include 'user/get-users.php';
include 'user/get-user.php';
include 'user/delete.php';
include 'user/ban.php';
include 'user/un-ban.php';
include 'user/list-ban.php';
include 'user/verify.php';
include 'user/enable.php';
include 'user/disable.php';
$userControllers = new ControllerGroup();
$userControllers->setGroupPath('/user');

View File

@ -18,7 +18,7 @@ DataValidator::with('CustomValidations', true);
* @apiUse INVALID_CREDENTIALS
*
* @apiSuccess {Object} data Information about an user
* @apiSuccess {String} data.name Name of the user
* @apiSuccess {String} data.name Name of the user
* @apiSuccess {String} data.email Email of the user
* @apiSuccess {Boolean} data.verified Indicates if the user is verified
* @apiSuccess {Object} data Information about an user
@ -48,7 +48,7 @@ class GetUserController extends Controller {
$ticketList = $user->sharedTicketList;
foreach($ticketList as $ticket) {
$parsedTicketList[] = $ticket->toArray();
$parsedTicketList[] = $ticket->toArray(true);
}
Response::respondSuccess([
@ -58,4 +58,4 @@ class GetUserController extends Controller {
'tickets' => $parsedTicketList
]);
}
}
}

View File

@ -1,4 +1,5 @@
<?php
use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /user/login Login
@ -39,6 +40,7 @@ class LoginController extends Controller {
private $userInstance;
private $rememberToken;
private $rememberExpiration;
public function validations() {
return [
@ -56,6 +58,8 @@ class LoginController extends Controller {
throw new Exception(ERRORS::SESSION_EXISTS);
}
$this->clearOldRememberTokens();
if ($this->checkInputCredentials() || $this->checkRememberToken()) {
if($this->userInstance->verificationToken !== null) {
throw new Exception(ERRORS::UNVERIFIED_USER);
@ -66,7 +70,7 @@ class LoginController extends Controller {
}
$this->createUserSession();
$this->createSessionCookie();
$this->createRememberToken();
if(Controller::request('staff')) {
$this->userInstance->lastLogin = Date::getCurrentDate();
$this->userInstance->store();
@ -106,7 +110,8 @@ class LoginController extends Controller {
'userEmail' => $userInstance->email,
'staff' => Controller::request('staff'),
'token' => Session::getInstance()->getToken(),
'rememberToken' => $this->rememberToken
'rememberToken' => $this->rememberToken,
'rememberExpiration' => $this->rememberExpiration
);
}
@ -138,18 +143,30 @@ class LoginController extends Controller {
return $userInstance;
}
private function createSessionCookie() {
$remember = Controller::request('remember');
private function clearOldRememberTokens() {
$currentDate = Date::getCurrentDate();
try {
RedBean::exec("DELETE FROM sessioncookie WHERE expiration_date < $currentDate");
} catch(Exception $e) {}
}
private function createRememberToken() {
$remember = Controller::request('remember');
if ($remember) {
$this->rememberToken = Hashing::generateRandomToken();
$this->rememberExpiration = Date::getNextDate(30);
$sessionCookie = new SessionCookie();
$sessionCookie->setProperties(array(
'user' => $this->userInstance,
'token' => $this->rememberToken,
'ip' => $_SERVER['REMOTE_ADDR'],
'creationDate' => date('d-m-Y (H:i:s)')
'creationDate' => Date::getCurrentDate(),
'expirationDate' => $this->rememberExpiration
));
$sessionCookie->store();
}
}

View File

@ -187,6 +187,18 @@
* @apiDefine USER_DISABLED
* @apiError {String} USER_DISABLED User is disabled
*/
/**
* @apiDefine INVALID_TEXT_1
* @apiError {String} INVALID_TEXT_1 text1 of mail template has an invalid syntax or missing variables
*/
/**
* @apiDefine INVALID_TEXT_2
* @apiError {String} INVALID_TEXT_2 text2 of mail template has an invalid syntax or missing variables
*/
/**
* @apiDefine INVALID_TEXT_3
* @apiError {String} INVALID_TEXT_3 text3 of mail template has an invalid syntax or missing variables
*/
class ERRORS {
const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS';
@ -237,4 +249,7 @@ class ERRORS {
const ALREADY_DISABLED = 'ALREADY_DISABLED';
const ALREADY_ENABLED = 'ALREADY_ENABLED';
const USER_DISABLED = 'USER_DISABLED';
const INVALID_TEXT_1 = 'INVALID_TEXT_1';
const INVALID_TEXT_2 = 'INVALID_TEXT_2';
const INVALID_TEXT_3 = 'INVALID_TEXT_3';
}

View File

@ -1,57 +0,0 @@
<?php
require_once 'data/MailTexts.php';
class InitialMails {
public static function getBody($templateCode, $texts) {
$templateFilePaths = [
'USER_SIGNUP' => 'data/mail-templates/user-signup.html',
'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_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',
'TICKET_RESPONDED' => 'data/mail-templates/ticket-responded.html',
'TICKET_CLOSED' => 'data/mail-templates/ticket-closed.html',
'TICKET_CREATED_STAFF' => 'data/mail-templates/ticket-created-staff.html',
];
array_shift($texts);
$matches = [];
foreach($texts as $key => $val) {
$matches[] = '{{' . $templateCode . '_MATCH_' . ($key + 1) . '}}';
}
return str_replace($matches, $texts, file_get_contents($templateFilePaths[$templateCode]));
}
public static function generateTemplateTexts($templateCode) {
$mailTexts = MailTexts::getTexts();
$templateTexts = [];
foreach($mailTexts as $language => $languageTexts) {
$templateTexts[$language] = [
'subject' => $languageTexts[$templateCode][0],
'body' => InitialMails::getBody($templateCode, $languageTexts[$templateCode]),
];
}
return $templateTexts;
}
public static function retrieve() {
return [
'USER_SIGNUP' => InitialMails::generateTemplateTexts('USER_SIGNUP'),
'USER_PASSWORD' => InitialMails::generateTemplateTexts('USER_PASSWORD'),
'USER_EMAIL' => InitialMails::generateTemplateTexts('USER_EMAIL'),
'PASSWORD_FORGOT' => InitialMails::generateTemplateTexts('PASSWORD_FORGOT'),
'USER_SYSTEM_DISABLED' => InitialMails::generateTemplateTexts('USER_SYSTEM_DISABLED'),
'USER_SYSTEM_ENABLED' => InitialMails::generateTemplateTexts('USER_SYSTEM_ENABLED'),
'TICKET_CREATED' => InitialMails::generateTemplateTexts('TICKET_CREATED'),
'TICKET_RESPONDED' => InitialMails::generateTemplateTexts('TICKET_RESPONDED'),
'TICKET_CLOSED' => InitialMails::generateTemplateTexts('TICKET_CLOSED'),
'TICKET_CREATED_STAFF' => InitialMails::generateTemplateTexts('TICKET_CREATED_STAFF'),
];
}
}

View File

@ -8,7 +8,6 @@ class MailTexts {
'Verify your account',
'Welcome to our support center, {{name}}!. We need you to verify this email in order to get access to your account.',
'Use this code in {{url}}/verify-token/{{to}}/{{verificationToken}} or click the button below.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Password edited - OpenSupports',
@ -25,28 +24,24 @@ class MailTexts {
'Recover password',
'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.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Access system changed - OpenSupports',
'Access system changed',
'Hello, {{name}}. The system to access tickets has changed.',
'You can access and see to your tickets by using your email and the ticket number.Click in the button below to see your tickets.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Account created - OpenSupports',
'Account created',
'Hello, {{name}}. We have created an account where you can access the tickets you have sent us.',
'You can access your account by using this email <i>({{to}})</i> and password below.Please change the password as soon as you log in.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Ticket created - OpenSupports',
'Ticket created',
'Hello, {{name}}. You have sent a new ticket titled <i>{{title}}</i> to our support center.',
'You can access to the ticket by its ticket number or you can click on the button below.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} New response - OpenSupports',
@ -65,7 +60,6 @@ class MailTexts {
'Ticket created',
'User {{name}} has created a new ticket titled <i>{{title}}</i>.',
'You can access to the ticket by its ticket number.',
'{{ticketNumber}}'
],
],
'cn' => [
@ -74,7 +68,6 @@ class MailTexts {
'验证您的帐户',
'欢迎来到我们的支援中心{{name}} !. 我们需要您验证此电子邮件才能访问您的帐户。',
'使用此代码 {{url}}/verify-token/{{to}}/{{verificationToken}} 或单击下面的按钮.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'密码已编辑 - OpenSupports',
@ -91,28 +84,24 @@ class MailTexts {
'恢复密码',
'喂 {{name}}。 您已要求恢复密码。',
'使用此代码 {{url}}/recover-password?email={{to}}&token={{token}} 或单击下面的按钮.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'访问系统更改 - OpenSupports',
'访问系统更改',
'您好,{{name}}。 访问票证的系统已更改。',
'您可以通过使用您的电子邮件和票号访问和查看您的机票。 点击下面的按钮查看您的票。',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'帐户已创建 - OpenSupports',
'帐户已创建',
'您好,{{name}}。 我们已经创建了一个帐户,您可以访问您发送给我们的票。',
'您可以在下面使用此电子邮件 <i>({{to}})</i> 和密码访问您的帐户。 请在登录后立即更改密码。',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} 已创建票证 - OpenSupports',
'票据创建',
'您好,{{name}}。 您已将一张名为 <i>{{title}}</i> 的新票发送到我们的支持中心。',
'您可以通过其票号访问票证。 或者你可以点击下面的按钮。',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} 新反应 - OpenSupports',
@ -131,7 +120,6 @@ class MailTexts {
'票据创建',
'用戶 {{name}}。 他創造了一個題為新票 <i>{{title}}</i>。',
'您可以通过其票号访问票证。',
'{{ticketNumber}}'
],
],
'de' => [
@ -140,7 +128,6 @@ class MailTexts {
'Überprüfen Sie Ihr Konto',
'Willkommen in unserem Support-Center, {{name}} !. Wir müssen Sie diese E-Mail bestätigen, um Zugang zu Ihrem Konto zu erhalten.',
'Verwenden Sie diesen Code in {{url}}/verify-token/{{to}}/{{verificationToken}} oder klicken Sie auf die Schaltfläche unten.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Passwort bearbeitet - OpenSupports',
@ -157,28 +144,24 @@ class MailTexts {
'Passwort wiederherstellen',
'Hallo, {{name}}. Sie haben aufgefordert, Ihr Passwort wiederherzustellen.',
'Verwenden Sie diesen Code in {{url}}/recover-password?email={{to}}&token={{token}} oder klicken Sie auf die Schaltfläche unten.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Access system changed - OpenSupports',
'Zugriffssystem geändert',
'Hallo, {{name}}. Das System für den Zugriff auf Tickets hat sich geändert.',
'können mit Ihren E-Mails und der Ticketnummer auf Ihre Tickets zugreifen und sie sehen.Klicken Sie auf die Schaltfläche unten, um Ihre Tickets zu sehen.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Account erstellt - OpenSupports',
'Account erstellt',
'Hallo, {{name}}. Wir haben ein Konto erstellt, wo Sie auf die Tickets zugreifen können, die Sie uns geschickt haben.',
'Sie können auf Ihr Konto zugreifen, indem Sie diese E-Mail <i>({{to}})</i> und das Passwort unten verwenden.Bitte ändern Sie das Passwort, sobald Sie sich anmelden.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Ticket erstellt - OpenSupports',
'Ticket erstellt',
'Hallo, {{name}}. Sie haben ein neues Ticket mit dem Titel <i>{{title}}</i> an unser Support-Center geschickt.',
'Sie können das Ticket nach der Fahrkartennummer erreichen. Oder klicken Sie auf die Schaltfläche unten.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Neue Antwort - OpenSupports',
@ -197,7 +180,6 @@ class MailTexts {
'Ticket erstellt',
'Der Benutzer {{name}} hat ein neues Ticket erstellt berechtigt <i>{{title}}</i>.',
'Sie können das Ticket nach der Fahrkartennummer erreichen.',
'{{ticketNumber}}'
],
],
'es' => [
@ -206,7 +188,6 @@ class MailTexts {
'Verifica tu cuenta',
'Bienvenido a nuestro contro de soporte, {{name}}!. Necesitamos que verifiques este email para poder acceder a tu cuenta.',
'Usá este código en {{url}}/verify-token/{{to}}/{{verificationToken}} o hacé click en el botón de abajo.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Contraseña a cambiado - OpenSupports',
@ -223,28 +204,24 @@ class MailTexts {
'Recuperar contraseña',
'Hola, {{name}}. Has requerido recuperar tu contraseña.',
'Usá este codigo en {{url}}/recover-password?email={{to}}&token={{token}} o hacé click en el botón de abajo.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Sistema de acceso cambiado - OpenSupports',
'Sistema de acceso cambiado',
'Hola, {{name}}. El sistema para acceder a los tickets ha cambiado.',
'Ahora podes acceder a los tickets usando tu email y el numero de ticket.Hacé click en el botón de abajo para poder ver los tickets.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Cuenta creada - OpenSupports',
'Cuenta creada',
'Hola, {{name}}. Hemos creado una cuenta donde puedes acceder a los tickets que nos has enviado.',
'Puedes acceder usando tu email <i>({{to}})</i> y el la contraseña de abajo.Por favor, cambia tu contraseña tan pronto como ingreses al panel de usuario.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Ticket creado - OpenSupports',
'Ticket creado',
'Hola, {{name}}. Has creado un nuevo ticket titulado <i>{{title}}</i> en nuestro sistema de soporte.',
'Puedes ver el ticket usando el numero de ticket prensentado abajo o puedes hacer click en el botón de más abajo.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Nueva respuesta - OpenSupports',
@ -263,7 +240,6 @@ class MailTexts {
'Ticket creado',
'El usuario {{name}} ha creado un nuevo ticket titulado <i>{{title}}</i>.',
'Puedes ver el ticket usando el numero de ticket prensentado abajo.',
'{{ticketNumber}}'
],
],
'fr' => [
@ -272,7 +248,6 @@ class MailTexts {
'Vérifiez votre compte',
'Bienvenue dans notre centre de support, {{name}}!. Nous vous demandons de vérifier cet e-mail afin d accéder à votre compte.',
'Utilisez ce code dans {{url}}/verify-token/{{to}}/{{verificationToken}} ou cliquez sur le bouton ci-dessous.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Mot de passe modifié - OpenSupports',
@ -289,28 +264,24 @@ class MailTexts {
'Récupérer mot de passe',
'Salut, {{name}}. Vous avez demandé à récupérer votre mot de passe.',
'Utilisez ce code dans {{url}}/recover-password?email={{to}}&token={{token}} ou cliquez sur le bouton ci-dessous.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Système d\'accès modifié - OpenSupports',
'Système d\'accès modifié',
'Bonjour, {{name}}.Le système d\'accès aux tickets a changé.',
'Vous pouvez accéder et voir vos billets en utilisant votre email et le numéro de ticket.Cliquez sur le bouton ci-dessous pour voir vos billets.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Compte créé - OpenSupports',
'Compte créé',
'Bonjour, {{name}}. Nous avons créé un compte où vous pouvez accéder aux billets que vous nous avez envoyés.',
'Vous pouvez accéder à votre compte en utilisant ce courriel <i>({{to}})</i> et votre mot de passe ci-dessous.Veuillez modifier le mot de passe dès que vous vous connectez.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Ticket créé - OpenSupports',
'Ticket créé',
'Bonjour, {{name}}. Vous avez envoyé un nouveau ticket intitulé <i>{{title}}</i> à notre centre de support.',
'Vous pouvez accéder au billet par son numéro de ticket. Ou vous pouvez cliquer sur le bouton ci-dessous.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Nouvelle réponse - OpenSupports',
@ -329,7 +300,6 @@ class MailTexts {
'Ticket créé',
'L\'utilisateur {{name}}. a créé un nouveau poste intitulé <i>{{title}}</i>.',
'Vous pouvez accéder au billet par son numéro de ticket.',
'{{ticketNumber}}'
],
],
'in' => [
@ -338,7 +308,6 @@ class MailTexts {
'अपने खाते को सत्यापित करें',
'हमारे समर्थन केंद्र में आपका स्वागत है {{name}}!. आपके खाते तक पहुंच प्राप्त करने के लिए हमें आपको यह ईमेल सत्यापित करने की आवश्यकता है।',
'इस कोड का उपयोग करें {{url}}/verify-token/{{to}}/{{verificationToken}} या नीचे दिए गए बटन पर क्लिक करें।',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'sandi diedit - OpenSupports',
@ -355,28 +324,24 @@ class MailTexts {
'गोपनीय शब्द पुन प्राप्त करे',
'नमस्ते {{name}}. आपने अपना पासवर्ड पुनर्प्राप्त करने का अनुरोध किया है',
'इस कोड का उपयोग करें {{url}}/recover-password?email={{to}}&token={{token}} या नीचे दिए गए बटन पर क्लिक करें.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'sistem akses berubah - OpenSupports',
'एक्सेस सिस्टम बदल गया',
'नमस्ते {{name}}. टिकट का उपयोग करने के लिए सिस्टम बदल गया है।',
'आप अपने ईमेल और टिकट नंबर का उपयोग करके अपने टिकट तक पहुंच सकते हैं और देख सकते हैं।अपने टिकट देखने के लिए नीचे दिए गए बटन पर क्लिक करें।',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Akun telah dibuat - OpenSupports',
'खाता बन गय',
'नमस्ते {{name}}. हमने एक खाता बनाया है, जहां आप हमारे द्वारा भेजे गए टिकटों तक पहुंच सकते हैं।',
'आप इस ईमेल <i>({{to}})</i> और नीचे दिए गए पासवर्ड का उपयोग करके अपने खाते का उपयोग कर सकते हैं।जैसे ही आप लॉग इन करते हैं, तभी पासवर्ड बदल दें।',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} tiket dibuat - OpenSupports',
'टिकट बनाय',
'नमस्ते {{name}}. आपने हमारे समर्थन केंद्र पर <i>{{title}}</i> नामक एक नया टिकट भेजा है.',
'आप अपने टिकट नंबर से टिकट तक पहुंच सकते हैं। या आप नीचे दिए गए बटन पर क्लिक कर सकते हैं।',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} tanggapan baru - OpenSupports',
@ -395,7 +360,6 @@ class MailTexts {
'टिकट बनाया',
'उपयोगकर्ता {{name}} हकदार एक नया पद बनाया गया है <i>{{title}}</i>.',
'आप अपने टिकट नंबर से टिकट तक पहुंच सकते हैं।',
'{{ticketNumber}}'
],
],
'it' => [
@ -404,7 +368,6 @@ class MailTexts {
'Verifica il tuo account',
'Benvenuto, {{name}}!. Devi verificare questa email per accedere al tuo account.',
'Clicca sul link {{url}}/verify-token/{{to}}/{{verificationToken}} o clicca sul pulsante qui sotto.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Password modificata - OpenSupports',
@ -421,28 +384,24 @@ class MailTexts {
'Recupera password',
'Ciao, {{name}}. Hai richiesto di recuperare la tua password.',
'Clicca sul link {{url}}/recover-password?email={{to}}&token={{token}} o clicca sul pulsante qui sotto.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Il sistema di accesso è cambiato - OpenSupports',
'Modifica sistema di accesso',
'Ciao, {{name}}. Il sistema di accesso ai tuoi tickets è cambiato.',
'Puoi accedere ai tuoi ticket usando la tua email e il numero del ticket.Clicca sul bottone qui sotto per vedere i tuoi tickets.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Account creato - OpenSupports',
'Account creato',
'Ciao, {{name}}. Abbiamo creato il tuo account.',
' Puoi accedere al tuo account utilizzando questa email <i>({{to}})</i> e la password qui sotto.Ti consigliamo di cambiare la password dopo il primo accesso.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} ticket creato - OpenSupports',
'Ticket inviato',
'Ciao, {{name}}. Hai inviato un nuovo ticket <i>{{title}}</i> al nostro centro si assistenza.',
'È possibile accedere al ticket attraverso il numero del ticket. Oppure puoi cliccare sul bottone qui sotto.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Ticket risposto - OpenSupports',
@ -461,7 +420,6 @@ class MailTexts {
'Ticket inviato',
'l\'utente {{name}} ha creato un nuovo titolo dal titolo <i>{{title}}</i>',
'È possibile accedere al ticket con il suo numero di ticket.',
'{{ticketNumber}}'
],
],
'jp' => [
@ -470,7 +428,6 @@ class MailTexts {
'アカウントを確認する',
'サポートセンターへようこそ, {{name}}!. アカウントにアクセスするには、このメールを確認する必要があります。',
'でこのコードを使用 {{url}}/verify-token/{{to}}/{{verificationToken}} 下のボタンをクリックしてください.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'パスワードの編集 - OpenSupports',
@ -487,28 +444,24 @@ class MailTexts {
'パスワードを回復',
'こんにちは、{{name}}。 パスワードの回復を要求しました。',
'でこのコードを使用 {{url}}/recover-password?email={{to}}&token={{token}} 下のボタンをクリックしてください.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'アクセスシステムが変更されました - OpenSupports',
'アクセスシステムが変更されました',
'こんにちは、{{name}}。 チケットにアクセスするシステムが変更されました。',
'あなたはあなたの電子メールとチケット番号を使ってチケットにアクセスして見ることができます。チケットを見るには、下のボタンをクリックしてください。',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'アカウントが作成されました - OpenSupports',
'アカウントが作成されました',
'こんにちは、{{name}}。 あなたが送ったチケットにアクセスできるアカウントを作成しました。',
'下記のメール<i>{{to}}</i>とパスワードを使用してアカウントにアクセスできます。ログインするとすぐにパスワードを変更してください。',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} チケットが作成されました - OpenSupports',
'チケットが作成されました',
'こんにちは、{{name}}。<i>{{title}}</i> という新しいチケットをサポートセンターにお送りしました。',
'そのチケット番号でチケットにアクセスできます。 または、下のボタンをクリックしてください。',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} 新しい応答 - OpenSupports',
@ -527,7 +480,6 @@ class MailTexts {
'チケットが作成されました',
'ユーザーは {{name}} 彼は題した新しいチケットを作成しました <i>{{title}}</i>。',
'そのチケット番号でチケットにアクセスできます。',
'{{ticketNumber}}'
],
],
'pt' => [
@ -536,7 +488,6 @@ class MailTexts {
'Verifique sua conta',
'Bem-vindo ao nosso centro de suporte, {{name}}!. Precisamos que você verifique este e-mail para acessar sua conta.',
'Use este código em {{url}}/verify-token/{{to}}/{{verificationToken}} ou clique no botão abaixo.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Senha editada - OpenSupports',
@ -553,28 +504,24 @@ class MailTexts {
'Recuperar senha',
'Olá, {{name}}. Você solicitou a recuperação da sua senha.',
'Use este código em {{url}}/recover-password?email={{to}}&token={{token}} ou clique no botão abaixo.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Sistema de acesso alterado - OpenSupports',
'Sistema de acesso alterado',
'Oi, {{name}}. O sistema de acesso aos tickets mudou.',
'Você pode acessar e ver seus bilhetes usando seu e-mail eo número do bilhete.Clique no botão abaixo para ver os seus bilhetes.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Conta criada - OpenSupports',
'Conta criada',
'Oi, {{name}}. Criamos uma conta onde você pode acessar os ingressos que você nos enviou.',
'Você pode acessar sua conta usando este e-mail <i>({{to}})</i> e a senha abaixo.Por favor, altere a senha assim que fizer login.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Ticket criado - OpenSupports',
'Ticket criado',
'Olá, {{name}}. Você enviou um novo ticket intitulado <i>{{title}}</i> para o nosso centro de suporte.',
'Você pode acessar o bilhete pelo seu número de bilhete. Ou você pode clicar no botão abaixo.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Nova resposta - OpenSupports',
@ -593,7 +540,6 @@ class MailTexts {
'Ticket criado',
'O usuário {{name}} criou um novo bilhete de direito <i>{{title}}</i>.',
'Você pode acessar o bilhete pelo seu número de bilhete.',
'{{ticketNumber}}'
],
],
'ru' => [
@ -602,7 +548,6 @@ class MailTexts {
'подтвердите ваш аккаунт',
'Добро пожаловать в наш центр поддержки, {{name}}!. Нам нужно, чтобы вы подтвердили это письмо, чтобы получить доступ к вашей учетной записи.',
'Используйте этот код в {{url}}/verify-token/{{to}}/{{verificationToken}} или нажмите кнопку ниже.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Пароль изменен - OpenSupports',
@ -619,28 +564,24 @@ class MailTexts {
'Восстановить пароль',
'Здравствуй, {{name}}. Вы запросили восстановить пароль.',
'Используйте этот код в {{url}}/recover-password?email={{to}}&token={{token}} или нажмите кнопку ниже.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Система доступа изменена - OpenSupports',
'Система доступа изменена',
'Здравствуйте, {{name}}. Система доступа к билетам изменилась.',
'Вы можете получить доступ к своим билетам и посмотреть их, используя свою электронную почту и номер билета.Нажмите кнопку ниже, чтобы увидеть свои билеты.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Аккаунт создан - OpenSupports',
'Аккаунт создан',
'Здравствуйте, {{name}}. Мы создали учетную запись, где вы можете получить доступ к билетам, которые вы нам отправили.',
'Вы можете получить доступ к своей учетной записи, используя это электронное письмо <i>({{to}})</i> и пароль ниже.Измените пароль, как только вы войдете в систему.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Создан билет - OpenSupports',
'Создан билет',
'Здравствуйте, {{name}}. Вы отправили новый билет с названием <i>{{title}}</i> в наш центр поддержки.',
'Вы можете получить доступ к билету по его номеру билета. Или вы можете нажать на кнопку ниже.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Новый ответ - OpenSupports',
@ -659,7 +600,6 @@ class MailTexts {
'Создан билет',
'Пользователь {{name}} создал новый билет под названием <i>{{title}}</i>.',
'Вы можете получить доступ к билету по его номеру билета.',
'{{ticketNumber}}'
],
],
'tr' => [
@ -668,7 +608,6 @@ class MailTexts {
'Hesabınızı doğrulayın',
'Destek merkezimize hoş geldiniz, {{name}}!. Hesabınıza erişebilmek için bu e-postayı doğrulamanız gerekiyor.',
'Bu kodu şu adreste kullanın {{url}}/verify-token/{{to}}/{{verificationToken}} veya aşağıdaki butona tıklayın.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Şifre düzenlendi - OpenSupports',
@ -685,28 +624,24 @@ class MailTexts {
'Şifre kurtarma',
'Merhaba, {{name}}. Şifrenizi geri yüklemenizi istediniz.',
'Bu kodu şu adreste kullanın {{url}}/recover-password?email={{to}}&token={{token}} veya aşağıdaki butona tıklayın.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Erişim sistemi değiştirildi - OpenSupports',
'Erişim sistemi değiştirildi',
'Merhaba, {{name}}. Biletleri erişmek için sistem değişti.',
'E-posta adresinizi ve bilet numaranızı kullanarak biletinize erişebilir ve biletlerini görebilirsiniz.Biletlerini görmek için aşağıdaki butona tıklayın.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Hesap oluşturuldu - OpenSupports',
'Hesap oluşturuldu',
'Merhaba, {{name}}. Bize gönderdiğiniz bilete erişebileceğiniz bir hesap oluşturduk.',
'Hesabınıza, <i>({{to}})</i> e-posta adresini kullanarak ve aşağıdaki şifreyle erişebilirsiniz.Lütfen giriş yaptığınızda şifreyi değiştirin.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Bilet oluşturuldu - OpenSupports',
'Bilet oluşturuldu',
'Merhaba, {{name}}. Destek merkezimize <i>{{title}}</i> başlıklı yeni bir bilet gönderdiniz.',
'Bilete bilet numarasından erişebilirsiniz. Ya da aşağıdaki düğmeyi tıklayabilirsiniz.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Yeni yanıt - OpenSupports',
@ -725,7 +660,6 @@ class MailTexts {
'Bilet oluşturuldu',
'Kullanıcı {{name}} başlıklı yeni bir bilet yarattı <i>{{title}}</i> .',
'Bilete bilet numarasından erişebilirsiniz.',
'{{ticketNumber}}'
],
],
'br' => [
@ -734,7 +668,6 @@ class MailTexts {
'Verifique sua conta',
'Bem-vindo ao nosso centro de suporte, {{name}}!. Precisamos que você verifique este e-mail para acessar sua conta.',
'Use este código em {{url}}/verify-token/{{to}}/{{verificationToken}} ou clique no botão abaixo.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Senha editada - OpenSupports',
@ -751,28 +684,24 @@ class MailTexts {
'Recuperar senha',
'Olá, {{name}}. Você solicitou a recuperação da sua senha.',
'Use este código em {{url}}/recover-password?email={{to}}&token={{token}} ou clique no botão abaixo.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Sistema de acesso alterado - OpenSupports',
'Sistema de acesso alterado',
'Oi, {{name}}. O sistema de acesso aos tickets mudou.',
'Você pode acessar e ver seus bilhetes usando seu e-mail eo número do bilhete.Clique no botão abaixo para ver os seus bilhetes.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Conta criada - OpenSupports',
'Conta criada',
'Oi, {{name}}. Criamos uma conta onde você pode acessar os ingressos que você nos enviou.',
'Você pode acessar sua conta usando este e-mail <i>({{to}})</i> e a senha abaixo.Por favor, altere a senha assim que fizer login.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Ticket criado - OpenSupports',
'Ticket criado',
'Olá, {{name}}. Você enviou um novo ticket intitulado <i>{{title}}</i> para o nosso centro de suporte.',
'Você pode acessar o bilhete pelo seu número de bilhete. Ou você pode clicar no botão abaixo.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Nova resposta - OpenSupports',
@ -791,7 +720,6 @@ class MailTexts {
'Ticket criado',
'O usuário {{name}} criou um novo bilhete de direito <i>{{title}}</i>.',
'Você pode acessar o bilhete pelo seu número de bilhete.',
'{{ticketNumber}}'
],
],
'gr' => [
@ -800,7 +728,6 @@ class MailTexts {
'Επιβεβαιώστε το λογαριασμό σας',
'Καλώς ήρθατε στο κέντρο υποστήριξης {{name}} !. Πρέπει να επαληθεύσετε αυτό το μήνυμα ηλεκτρονικού ταχυδρομείου για να αποκτήσετε πρόσβαση στο λογαριασμό σας.',
'Χρησιμοποιήστε αυτόν τον κωδικό στο {{url}}/verify-token/{{to}}/{{verificationToken}} ή κάντε κλικ στο παρακάτω κουμπί.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Ο κωδικός επεξεργασίας τροποποιήθηκε- OpenSupports',
@ -817,29 +744,24 @@ class MailTexts {
'Ανάκτηση κωδικού πρόσβασης',
'Γεια σου, {{name}}. Ζητήσατε να ανακτήσετε τον κωδικό πρόσβασής σας.',
'Χρησιμοποιήστε αυτόν τον κωδικό στο {{url}} / recover-password? Email = {{to}} & token = {{token}} ή κάντε κλικ στο παρακάτω κουμπί.',
'{{token}}'
],
'USER_SYSTEM_DISABLED' => [
'Το σύστημα πρόσβασης άλλαξε - OpenSupports',
'Το σύστημα πρόσβασης άλλαξε',
'«Γεια σας, {{name}}. Το σύστημα πρόσβασης στα εισιτήρια έχει αλλάξει. ',
'Μπορείτε να έχετε πρόσβαση και να δείτε τα εισιτήριά σας χρησιμοποιώντας το email σας και τον αριθμό του εισιτηρίου.Κάντε κλικ στο κουμπί παρακάτω για να δείτε τα εισιτήριά σας.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Δημιουργία λογαριασμού - OpenSupports',
'Λογαριασμός που δημιουργήθηκε',
'«Γεια σας, {{name}}. Δημιουργήσαμε ένα λογαριασμό στον οποίο μπορείτε να έχετε πρόσβαση στα εισιτήρια που μας έχετε στείλει. ',
'Μπορείτε να αποκτήσετε πρόσβαση στο λογαριασμό σας χρησιμοποιώντας αυτό το μήνυμα ηλεκτρονικού ταχυδρομείου <i> ({{to}}) </ i> και τον κωδικό πρόσβασης παρακάτω.Παρακαλώ αλλάξτε τον κωδικό πρόσβασης μόλις συνδεθείτε.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Δημιουργήθηκε εισιτήριο - OpenSupports',
'Δημιουργήθηκε εισιτήριο',
'Χαίρετε, {{name}}.Έχετε στείλει ένα νέο εισιτήριο με τίτλο <i> {{title}} </ i> στο κέντρο υποστήριξης.',
'Μπορείτε να έχετε πρόσβαση στο εισιτήριο με τον αριθμό εισιτηρίου του ή μπορείτε να κάνετε κλικ στο παρακάτω κουμπί.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Νέα απάντηση- OpenSupports',
@ -858,7 +780,6 @@ class MailTexts {
'Incident aangemaakt',
'Gebruiker {{name}} heeft een nieuw incident aangemaakt met onderwerp <i>{{title}}</i>.',
'Bekijk dit incident via het incidentnummer.',
'{{ticketNumber}}'
],
],
'nl' => [
@ -867,7 +788,6 @@ class MailTexts {
'Verifieer uw account',
'Welkom bij het Support Center, {{name}}!. U moet uw account verifiëren om toegang te krijgen tot het systeem.',
'Use this code in {{url}}/verify-token/{{to}}/{{verificationToken}} or click the button below.',
'{{verificationToken}}'
],
'USER_PASSWORD' => [
'Wachtwoord is aangepast - OpenSupports',
@ -890,22 +810,18 @@ class MailTexts {
'Toegang tot incidenten is gewijzigd',
'Hallo, {{name}}. De toegang tot incidenten is gewijzigd.',
'U kunt uw incidenten bekijken d.m.v. uw email en het incident nummer.Klik op de knop hieronder om uw tickets te bekijken.',
'{{tickets}}'
],
'USER_SYSTEM_ENABLED' => [
'Account is aangemaakt - OpenSupports',
'Account Aangemaakt',
'Hallo, {{name}}. We hebben een account voor u aangemaakt waarmee u uw incidenten kunt bekijken.',
'U kunt inloggen met dit email adres <i>({{to}})</i> en onderstaande wachtwoord.Verander alstublieft het wachtwoord na het inloggen.',
'{{password}}'
],
'TICKET_CREATED' => [
'#{{ticketNumber}} Incident aangemaakt - OpenSupports',
'Incident aangemaakt',
'Hallo, {{name}}. U heeft zojuist een incident aangemaakt met onderwerp <i>{{title}}</i> in ons support center.',
'U kunt dit incident bekijken via het incidentnummer of via de knop hieronder.',
'{{ticketNumber}}'
],
'TICKET_RESPONDED' => [
'#{{ticketNumber}} Nieuw antwoord - OpenSupports',
@ -924,7 +840,6 @@ class MailTexts {
'Incident is gesloten',
'Hallo, {{name}}. Een incident met onderwerp <i>{{title}}</i> is gesloten.',
'U kunt dit incident bekijken via het incidentnummer. Of klik op de knop hieronder.',
'{{ticketNumber}}'
],
],
];

View File

@ -302,8 +302,8 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>

View File

@ -302,8 +302,8 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>
@ -345,7 +345,7 @@
</tr>
<tr>
<td class="code-block">
{{TICKET_CREATED_STAFF_MATCH_4}}
{{ticketNumber}}
</td>
</tr>
</table>

View File

@ -302,8 +302,8 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>
@ -345,7 +345,7 @@
</tr>
<tr>
<td class="code-block">
{{TICKET_CREATED_MATCH_4}}
{{ticketNumber}}
</td>
</tr>
<tr>

View File

@ -302,8 +302,8 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>

View File

@ -302,8 +302,8 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>

View File

@ -302,9 +302,9 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
</tr>
</table>
</center>

View File

@ -302,8 +302,8 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>
@ -345,7 +345,7 @@
</tr>
<tr>
<td class="code-block">
{{PASSWORD_FORGOT_MATCH_4}}
{{token}}
</td>
</tr>
<tr>

View File

@ -302,8 +302,8 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>
@ -345,7 +345,7 @@
</tr>
<tr>
<td class="code-block">
{{USER_SIGNUP_MATCH_4}}
{{verificationToken}}
</td>
</tr>
<tr>

View File

@ -302,8 +302,8 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>
@ -345,7 +345,7 @@
</tr>
<tr>
<td class="code-block">
{{USER_SYSTEM_DISABLED_MATCH_4}}
{{tickets}}
</td>
</tr>
<tr>

View File

@ -302,8 +302,8 @@
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>
@ -345,7 +345,7 @@
</tr>
<tr>
<td class="code-block">
{{USER_SYSTEM_ENABLED_MATCH_4}}
{{password}}
</td>
</tr>
<tr>

View File

@ -15,49 +15,10 @@ if(defined('MYSQL_HOST') && defined('MYSQL_DATABASE') && defined('MYSQL_USER') &
\Slim\Slim::registerAutoLoader();
$app = new \Slim\Slim();
// LOAD LIBRARIES
include_once 'libs/Controller.php';
include_once 'libs/ControllerGroup.php';
include_once 'libs/Hashing.php';
include_once 'libs/MailSender.php';
include_once 'libs/Date.php';
include_once 'libs/DataStoreList.php';
include_once 'libs/LinearCongruentialGenerator.php';
include_once 'libs/FileManager.php';
include_once 'libs/FileDownloader.php';
include_once 'libs/FileUploader.php';
Controller::init();
// LOAD DATA
spl_autoload_register(function ($class) {
$classPath = "data/{$class}.php";
if(file_exists($classPath)) {
include_once $classPath;
}
});
// LOAD MODELS
spl_autoload_register(function ($class) {
$classPath = "models/{$class}.php";
if(file_exists($classPath)) {
include_once $classPath;
}
});
// LOAD CUSTOM VALIDATIONS
include_once 'libs/validations/dataStoreId.php';
include_once 'libs/validations/userEmail.php';
include_once 'libs/validations/staffEmail.php';
include_once 'libs/validations/captcha.php';
include_once 'libs/validations/validLanguage.php';
include_once 'libs/validations/validTicketNumber.php';
// LOAD CONTROLLERS
foreach (glob('controllers/*.php') as $controller) {
include_once $controller;
}
Controller::init();
$app->run();

View File

@ -1,7 +1,4 @@
<?php
require_once 'libs/Validator.php';
require_once 'models/Session.php';
use RedBeanPHP\Facade as RedBean;
abstract class Controller {
@ -103,9 +100,6 @@ abstract class Controller {
if(!$totalImages) return [];
$maxSize = Setting::getSetting('max-size')->getValue();
$fileGap = Setting::getSetting('file-gap')->getValue();
$fileFirst = Setting::getSetting('file-first-number')->getValue();
$fileQuantity = Setting::getSetting('file-quantity');
$fileUploader = FileUploader::getInstance();
$fileUploader->setMaxSize($maxSize);
@ -121,13 +115,10 @@ abstract class Controller {
$imagePaths = [];
$url = Setting::getSetting('url')->getValue();
for($i=0;$i<$totalImages;$i++) {
$fileUploader->setGeneratorValues($fileGap, $fileFirst, $fileQuantity->getValue());
$fileUploader->upload("image_$i");
$imagePaths[] = $url . '/api/system/download?file=' . $fileUploader->getFileName();
$fileQuantity->value++;
}
$fileQuantity->store();
return $imagePaths;
}
@ -138,17 +129,11 @@ abstract class Controller {
if(!isset($_FILES['file'])) return '';
$maxSize = Setting::getSetting('max-size')->getValue();
$fileGap = Setting::getSetting('file-gap')->getValue();
$fileFirst = Setting::getSetting('file-first-number')->getValue();
$fileQuantity = Setting::getSetting('file-quantity');
$fileUploader = FileUploader::getInstance();
$fileUploader->setMaxSize($maxSize);
$fileUploader->setGeneratorValues($fileGap, $fileFirst, $fileQuantity->getValue());
if($fileUploader->upload('file')) {
$fileQuantity->value++;
$fileQuantity->store();
return $fileUploader;
} else {

View File

@ -1,16 +1,14 @@
<?php
require_once 'models/DataStore.php';
class DataStoreList implements IteratorAggregate {
private $list = [];
public static function getList($type, $beanList) {
$dataStoreList = new DataStoreList();
foreach ($beanList as $bean) {
$dataStoreList->add(new $type($bean));
}
return $dataStoreList;
}
@ -51,21 +49,21 @@ class DataStoreList implements IteratorAggregate {
public function toBeanList() {
$beanList = [];
foreach($this->list as $item) {
$item->updateBeanProperties();
$beanList[] = $item->getBeanInstance();
}
return $beanList;
}
public function toArray() {
public function toArray($minimized = false) {
$array = [];
foreach($this->list as $item) {
$item->updateBeanProperties();
$array[] = $item->toArray();
$array[] = $item->toArray($minimized);
}
return $array;
@ -80,4 +78,4 @@ class DataStoreList implements IteratorAggregate {
return -1;
}
}
}

View File

@ -7,4 +7,8 @@ class Date {
public static function getPreviousDate($days = 1) {
return date('YmdHi', strtotime(" -$days day "));
}
public static function getNextDate($days = 1) {
return date('YmdHi', strtotime(" +$days day "));
}
}

View File

@ -2,8 +2,6 @@
class FileUploader extends FileManager {
private $maxSize = 1;
private $linearCongruentialGenerator;
private $linearCongruentialGeneratorOffset;
private $fileName;
private $permission;
private $storage;
@ -71,12 +69,10 @@ class FileUploader extends FileManager {
$newName = preg_replace('/[^a-zA-Z0-9\d\.\-]/', '_', $newName);
$result = "";
if ($this->linearCongruentialGenerator instanceof LinearCongruentialGenerator) {
if($this->permission) $result = $this->permission . '_';
else $result = '';
if($this->permission) $result = $this->permission . '_';
else $result = '';
$result .= $this->linearCongruentialGenerator->generate($this->linearCongruentialGeneratorOffset) . '_' . $newName;
}
$result .= substr(Hashing::generateRandomToken(), 0, 6) . '_' . $newName;
return $result;
}
@ -92,14 +88,6 @@ class FileUploader extends FileManager {
else $this->permission = '';
}
public function setGeneratorValues($gap, $first, $offset) {
$this->linearCongruentialGenerator = new LinearCongruentialGenerator();
$this->linearCongruentialGeneratorOffset = $offset;
$this->linearCongruentialGenerator->setGap($gap);
$this->linearCongruentialGenerator->setFirst($first);
}
public function setMaxSize($maxSize) {
$this->maxSize = $maxSize;
}

View File

@ -34,10 +34,13 @@ class MailSender {
}
public function setTemplate($type, $config) {
$mailTemplate = MailTemplate::getTemplate($type);
$compiledMailContent = $mailTemplate->compile($config);
$mailTemplate = MailTemplate::getMailTemplate($type);
$this->mailOptions = array_merge($this->mailOptions, $compiledMailContent);
$this->mailOptions = array_merge($this->mailOptions, [
'subject' => $mailTemplate->getSubject($config),
'body' => $mailTemplate->getBody($config),
'to' => $config['to'],
]);
}
public function send() {

View File

@ -1,6 +1,4 @@
<?php
require_once 'libs/Controller.php';
use Respect\Validation\Validator as DataValidator;
class ValidationException extends Exception {}

View File

@ -33,13 +33,13 @@ abstract class DataStore {
}
public static function find($query = '', $matches = []) {
$beanList = RedBean::find(static::TABLE, $query, $matches);
return DataStoreList::getList(ucfirst(static::TABLE), $beanList);
}
public static function findOne($query = '', $matches = []) {
$bean = RedBean::findOne(static::TABLE, $query, $matches);
return ($bean) ? new static($bean) : new NullDataStore();
}
@ -150,6 +150,14 @@ abstract class DataStore {
}
}
public function withCondition($condition, $values) {
return new static($this->_bean->withCondition($condition, $values));
}
public function countShared($shared) {
return $this->_bean->countShared($shared);
}
private function updateBeanProp($key, $value) {
if ($value instanceof DataStoreList) {
$this->_bean[$key] = $value->toBeanList();

View File

@ -8,7 +8,9 @@ use RedBeanPHP\Facade as RedBean;
* @apiParam {String} type The type of the mail template.
* @apiParam {String} subject The subject of the mail template.
* @apiParam {string} language The language of the mail template.
* @apiParam {String} body The body of the mail template.
* @apiParam {String} text1 First paragraph of the mail template.
* @apiParam {String} text2 Second paragraph of the mail template.
* @apiParam {String} text3 Thrid paragraph of the mail template.
*/
class MailTemplate extends DataStore {
@ -24,11 +26,26 @@ class MailTemplate extends DataStore {
const TICKET_CLOSED = 'TICKET_CLOSED';
const TICKET_CREATED_STAFF = 'TICKET_CREATED_STAFF';
public static function getTemplate($type) {
public static function getFilePaths() {
return [
'USER_SIGNUP' => 'data/mail-templates/user-signup.html',
'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_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',
'TICKET_RESPONDED' => 'data/mail-templates/ticket-responded.html',
'TICKET_CLOSED' => 'data/mail-templates/ticket-closed.html',
'TICKET_CREATED_STAFF' => 'data/mail-templates/ticket-created-staff.html',
];
}
public static function getMailTemplate($template) {
$globalLanguage = Setting::getSetting('language')->value;
$bean = RedBean::findOne(MailTemplate::TABLE, 'type = :type AND language = :language', array(
':type' => $type,
$bean = RedBean::findOne(MailTemplate::TABLE, 'template = :template AND language = :language', array(
':template' => $template,
':language' => $globalLanguage
));
@ -37,19 +54,36 @@ class MailTemplate extends DataStore {
public static function getProps() {
return [
'type',
'template',
'subject',
'language',
'body'
'text1',
'text2',
'text3',
];
}
public function compile($config) {
return [
'body' => $this->compileString($this->body, $config),
'subject' => $this->compileString($this->subject, $config),
'to' => $config['to']
];
public function getSubject($config) {
return $this->compileString($this->subject, $config);
}
public function getBody($config) {
$templateFilePaths = MailTemplate::getFilePaths();
$texts = [
$this->text1, $this->text2, $this->text3, $this->text4,
];
$matches = [];
foreach($texts as $key => $val) {
$matches[] = '{{' . $this->template . '_MATCH_' . ($key + 1) . '}}';
}
$matches[] = '{{IMAGE_HEADER_URL}}';
$texts[] = Setting::getSetting('mail-template-header-image')->value;
$body = str_replace($matches, $texts, file_get_contents($templateFilePaths[$this->template]));
return $this->compileString($body, $config);
}
public function compileString($string, $config) {
@ -61,12 +95,15 @@ class MailTemplate extends DataStore {
return $compiledString;
}
public function toArray() {
return [
'type' => $this->type,
'template' => $this->template,
'subject' => $this->subject,
'language' => $this->language,
'body' => $this->body,
'text1' => $this->text1,
'text2' => $this->text2,
'text3' => $this->text3,
];
}
}

View File

@ -96,25 +96,25 @@ class Ticket extends DataStore {
public function generateUniqueTicketNumber() {
$linearCongruentialGenerator = new LinearCongruentialGenerator();
$ticketQuantity = Ticket::count();
if ($ticketQuantity === 0) {
if (Ticket::count() === 0) {
$ticketNumber = Setting::getSetting('ticket-first-number')->value;
} else {
$lastTicketId = Ticket::findOne(' ORDER BY id DESC')->id;
$linearCongruentialGenerator->setGap(Setting::getSetting('ticket-gap')->value);
$linearCongruentialGenerator->setFirst(Setting::getSetting('ticket-first-number')->value);
$ticketNumber = $linearCongruentialGenerator->generate($ticketQuantity);
$ticketNumber = $linearCongruentialGenerator->generate($lastTicketId + 1);
}
return $ticketNumber;
}
public function toArray() {
public function toArray($minimized = false) {
return [
'ticketNumber' => $this->ticketNumber,
'title' => $this->title,
'content' => $this->content,
'content' => $minimized ? strip_tags($this->content) : $this->content,
'department' => [
'id' => $this->department->id,
'name' => $this->department->name
@ -128,7 +128,7 @@ class Ticket extends DataStore {
'priority' => $this->priority,
'author' => $this->authorToArray(),
'owner' => $this->ownerToArray(),
'events' => $this->eventsToArray()
'events' => $minimized ? [] : $this->eventsToArray()
];
}

View File

@ -10,6 +10,7 @@ namespace RedBeanPHP {
self::setStatics(array(
'trash' => parent::stub(),
'store' => parent::stub(),
'exec' => parent::stub(),
'dispense' => parent::stub()->returns(new \BeanMock())
));
}

View File

@ -8,10 +8,9 @@ include_once 'tests/__mocks__/SessionMock.php';
include_once 'tests/__mocks__/UserMock.php';
include_once 'tests/__mocks__/HashingMock.php';
include_once 'tests/__mocks__/SessionCookieMock.php';
include_once 'tests/__mocks__/RedBeanMock.php';
include_once 'data/ERRORS.php';
include_once 'controllers/user/login.php';
use PHPUnit\Framework\TestCase;
class LoginControllerTest extends TestCase {
@ -45,7 +44,8 @@ class LoginControllerTest extends TestCase {
'userEmail' => 'MOCK_EMAIL',
'staff' => false,
'token' => 'TEST_TOKEN',
'rememberToken' => null
'rememberToken' => null,
'rememberExpiration' => Date::getNextDate(30)
)));
}

View File

@ -1,6 +1,4 @@
<?php
include_once 'libs/Hashing.php';
use PHPUnit\Framework\TestCase;
class HashingTest extends TestCase {
@ -53,7 +51,7 @@ class HashingTest extends TestCase {
foreach($nonPrimes as $number) $this->assertFalse(Hashing::isPrime($number));
}
public function testShouldGenerateRandsomPrime() {
public function testShouldGenerateRandomPrime() {
$TEST_TIMES = 10;
for ($i = 0; $i < $TEST_TIMES; $i++) {
@ -65,7 +63,6 @@ class HashingTest extends TestCase {
$this->assertTrue($min < $number1 && $number1 < $max);
$this->assertTrue($min < $number2 && $number2 < $max);
$this->assertNotEquals($number1, $number2);
$this->assertTrue(Hashing::isPrime($number1));
$this->assertTrue(Hashing::isPrime($number2));
}

View File

@ -1,7 +1,4 @@
<?php
include_once 'libs/Hashing.php';
include_once 'libs/LinearCongruentialGenerator.php';
use PHPUnit\Framework\TestCase;
class LinearCongruentialGeneratorTest extends TestCase {

View File

@ -1,13 +1,10 @@
<?php
include_once 'tests/__lib__/Mock.php';
include_once 'tests/__mocks__/RespectMock.php';
include_once 'tests/__mocks__/SettingMock.php';
include_once 'tests/__mocks__/APIKeyMock.php';
include_once 'tests/__mocks__/ControllerMock.php';
include_once 'tests/__mocks__/ReCaptchaMock.php';
include_once 'libs/validations/captcha.php';
use PHPUnit\Framework\TestCase;
class CaptchaValidationTest extends TestCase {

View File

@ -3,7 +3,6 @@ include_once 'tests/__lib__/Mock.php';
include_once 'tests/__mocks__/BeanMock.php';
include_once 'tests/__mocks__/SlimMock.php';
include_once 'tests/__mocks__/RedBeanMock.php';
include_once 'models/DataStore.php';
use RedBeanPHP\Facade as RedBean;
use PHPUnit\Framework\TestCase;

View File

@ -3,7 +3,6 @@ include_once 'tests/__lib__/Mock.php';
include_once 'tests/__mocks__/BeanMock.php';
include_once 'tests/__mocks__/SettingMock.php';
include_once 'tests/__mocks__/RedBeanMock.php';
include_once 'models/MailTemplate.php';
use RedBeanPHP\Facade as RedBean;
use PHPUnit\Framework\TestCase;
@ -20,11 +19,11 @@ class MailTemplateTest extends TestCase {
}
public function testGetTemplateShouldReturnSpecifiedTemplate() {
$mailTemplate = MailTemplate::getTemplate(MailTemplate::USER_SIGNUP);
$mailTemplate = MailTemplate::getMailTemplate(MailTemplate::USER_SIGNUP);
$this->assertEquals('TEST_TYPE', $mailTemplate->type);
$this->assertTrue(Redbean::get('findOne')->hasBeenCalledWithArgs('mailtemplate', 'type = :type AND language = :language', array(
':type' => 'USER_SIGNUP',
$this->assertEquals('USER_SIGNUP', $mailTemplate->type);
$this->assertTrue(Redbean::get('findOne')->hasBeenCalledWithArgs('mailtemplate', 'template = :template AND language = :language', array(
':template' => 'USER_SIGNUP',
':language' => 'MOCK_SETTING_VALUE'
)));
}
@ -32,23 +31,31 @@ class MailTemplateTest extends TestCase {
public function testCompilation() {
$mailTemplate = new MailTemplate();
$mailTemplate->setProperties([
'template' => 'USER_SIGNUP',
'subject' => 'Welcoming to {{to}}',
'body' => 'Welcome, {{userName}} to our team'
'text1' => 'Welcome, {{userName}} to our team'
]);
$result = $mailTemplate->compile([
$resultSubject = $mailTemplate->getSubject([
'to' => 'cersei@opensupports.com',
'userName' => 'Cersei Lannister',
]);
$this->assertEquals($result['subject'], 'Welcoming to cersei@opensupports.com');
$this->assertEquals($result['body'], 'Welcome, Cersei Lannister to our team');
$resultBody = $mailTemplate->getBody([
'to' => 'cersei@opensupports.com',
'userName' => 'Cersei Lannister',
]);
$this->assertEquals($resultSubject, 'Welcoming to cersei@opensupports.com');
$this->assertContains('Welcome, Cersei Lannister to our team', $resultBody);
}
private function getMockTemplateBean() {
$mailTemplateBean = new BeanMock();
$mailTemplateBean->type = 'TEST_TYPE';
$mailTemplateBean->body = 'Some body';
$mailTemplateBean->type = 'USER_SIGNUP';
$mailTemplateBean->text1 = 'Text1';
$mailTemplateBean->text2 = 'Text1';
$mailTemplateBean->text3 = 'Text1';
$mailTemplateBean->subject = 'Some subject';
$mailTemplateBean->language = 'en';

View File

@ -1,7 +1,6 @@
<?php
include_once 'tests/__lib__/Mock.php';
include_once 'tests/__mocks__/SlimMock.php';
include_once 'models/Response.php';
use PHPUnit\Framework\TestCase;

View File

@ -50,9 +50,7 @@ require './system/add-department.rb'
require './system/edit-department.rb'
require './system/delete-department.rb'
require './staff/last-events.rb'
require './system/get-mail-templates.rb'
require './system/edit-mail-template.rb'
require './system/recover-mail-template.rb'
require './system/mail-templates.rb'
require './system/disable-registration.rb'
require './system/enable-registration.rb'
require './system/add-api-key.rb'

View File

@ -2,13 +2,14 @@ describe '/staff/get-new-tickets' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
it 'should get news tickets' do
it 'should get new tickets' do
result = request('/staff/get-new-tickets', {
page: 1,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
(result['data'].size).should.equal(9)
(result['data']['tickets'].size).should.equal(8)
end
end

View File

@ -7,21 +7,24 @@ describe '/staff/get-tickets' do
ticket = $database.getRow('ticket', 1 , 'id')
request('/staff/assign-ticket', {
ticketNumber: ticket['ticket_number'],
page: 1,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
ticket = $database.getRow('ticket', 2 , 'id')
request('/staff/assign-ticket', {
ticketNumber: ticket['ticket_number'],
page: 1,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
result = request('/staff/get-tickets', {
page: 1,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
(result['data'].size).should.equal(5)
(result['data']['tickets'].size).should.equal(5)
end
end

View File

@ -1,22 +0,0 @@
describe'system/edit-mail-template' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
it 'should edit mail template' do
result= request('/system/edit-mail-template', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
language: 'en',
templateType: 'USER_SIGNUP',
subject: 'new subject',
body: 'new message'
})
(result['status']).should.equal('success')
row = $database.getRow('mailtemplate', 1, 'id')
(row['subject']).should.equal('new subject')
(row['body']).should.equal('new message')
end
end

View File

@ -1,15 +0,0 @@
describe'system/get-mail-templates' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
it 'should get mail templates' do
result= request('/system/get-mail-templates', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
(result['data'].size).should.equal(140)
end
end

View File

@ -0,0 +1,134 @@
describe 'Mail templates' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
describe 'system/get-mail-template' do
it 'should get USER_SIGNUP mail template' do
result = request('/system/get-mail-template', {
template: 'USER_SIGNUP',
language: 'en',
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
(result['data']['template']).should.equal('USER_SIGNUP')
(result['data']['subject']).should.equal('Signup {{to}} - OpenSupports')
(result['data']['text1']).should.equal('Verify your account')
(result['data']['text2']).should.equal('Welcome to our support center, {{name}}!. We need you to verify this email in order to get access to your account.')
(result['data']['text3']).should.equal('Use this code in {{url}}/verify-token/{{to}}/{{verificationToken}} or click the button below.')
end
it 'should get USER_EMAIL mail template' do
result = request('/system/get-mail-template', {
template: 'USER_EMAIL',
language: 'de',
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
(result['data']['template']).should.equal('USER_EMAIL')
(result['data']['subject']).should.equal('E-Mail bearbeitet - OpenSupports')
(result['data']['text1']).should.equal('E-Mail geändert')
(result['data']['text2']).should.equal('Hallo, {{name}}. Wir möchten Sie darüber informieren, dass Ihre E-Mail von Ihrem Kundenbereich zu {{newemail}} geändert wurde.')
(result['data']['text3']).should.equal('')
end
it 'should not get mail template if language or type invalid' do
result = request('/system/get-mail-template', {
template: 'USER_EMAILS',
language: 'de',
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_TEMPLATE')
result = request('/system/get-mail-template', {
template: 'USER_EMAIL',
language: 'wa',
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_TEMPLATE')
end
end
describe 'system/edit-mail-template' do
it 'should edit mail template' do
result = request('/system/edit-mail-template', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
language: 'en',
template: 'USER_SIGNUP',
subject: 'new subject',
text1: 'new text1',
text2: 'new text2 {{name}}',
text3: 'new text3 {{url}}/verify-token/{{to}}/{{verificationToken}}',
})
(result['status']).should.equal('success')
row = $database.getRow('mailtemplate', 1, 'id')
(row['template']).should.equal('USER_SIGNUP')
(row['subject']).should.equal('new subject')
(row['text1']).should.equal('new text1')
(row['text2']).should.equal('new text2 {{name}}')
(row['text3']).should.equal('new text3 {{url}}/verify-token/{{to}}/{{verificationToken}}')
end
it 'should fail if one of the texts has invalid syntax' do
result = request('/system/edit-mail-template', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
language: 'en',
template: 'USER_SIGNUP',
subject: 'new subject',
text1: 'new text1',
text2: 'new text2',
text3: 'new text3 {{url}}/verify-token/{{to}}/{{verificationToken}}',
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_TEXT_2')
row = $database.getRow('mailtemplate', 1, 'id')
(row['template']).should.equal('USER_SIGNUP')
(row['subject']).should.equal('new subject')
(row['text1']).should.equal('new text1')
(row['text2']).should.equal('new text2 {{name}}')
(row['text3']).should.equal('new text3 {{url}}/verify-token/{{to}}/{{verificationToken}}')
end
end
describe 'system/recover-mail-template' do
it 'should recover mail template' do
result = request('/system/recover-mail-template', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
language: 'en',
template: 'USER_SIGNUP',
})
(result['status']).should.equal('success')
row = $database.getRow('mailtemplate', 1, 'id')
(row['template']).should.equal('USER_SIGNUP')
(row['subject']).should.equal('Signup {{to}} - OpenSupports')
(row['text1']).should.equal('Verify your account')
(row['text2']).should.equal('Welcome to our support center, {{name}}!. We need you to verify this email in order to get access to your account.')
(row['text3']).should.equal('Use this code in {{url}}/verify-token/{{to}}/{{verificationToken}} or click the button below.')
end
end
end

View File

@ -1,19 +0,0 @@
describe'system/recover-mail-template' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
it 'should recover mail template' do
result= request('/system/recover-mail-template', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
language: 'en',
templateType: 'USER_SIGNUP',
})
(result['status']).should.equal('success')
row = $database.getRow('mailtemplate', 1, 'id')
(row['subject']).should.equal('Signup {{to}} - OpenSupports')
end
end

View File

@ -44,28 +44,38 @@ describe '/user/login' do
(result['data']['staff']).should.equal('true')
end
it 'should return remember token' do
it 'should work with remember token' do
request('/user/logout', {})
result = request('/user/login', {
email: @loginEmail,
password: @loginPass,
remember: true
remember: 1
})
(result['status']).should.equal('success')
@rememberToken = result['data']['rememberToken']
@userid = result['data']['userId']
end
@userId = result['data']['userId']
it 'should login with token' do
request('/user/logout', {})
result = request('/user/login', {
rememberToken: @rememberToken,
userId: @userid
userId: @userId,
rememberToken: '12abc',
remember: 1
})
(result['status']).should.equal('fail')
result = request('/user/login', {
userId: 1,
rememberToken: @rememberToken,
remember: 1
})
(result['status']).should.equal('fail')
result = request('/user/login', {
userId: @userId,
rememberToken: @rememberToken,
remember: 1
})
(result['status']).should.equal('success')
(result['data']['userId']).should.equal(@userid)
end
end