Merge branch 'master' into master

This commit is contained in:
Guillermo Giuliana 2019-12-20 11:35:51 -03:00 committed by GitHub
commit b9e4a55b91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1297 additions and 213 deletions

View File

@ -23,6 +23,7 @@ class ActivityRow extends React.Component {
'EDIT_SETTINGS',
'SIGNUP',
'INVITE',
'ADD_TOPIC',
'ADD_ARTICLE',
'DELETE_TOPIC',
@ -65,9 +66,7 @@ class ActivityRow extends React.Component {
<div className="activity-row">
<Icon {...this.getIconProps()} className="activity-row__icon"/>
<span>
<Link className="activity-row__name-link" to={this.getNameLinkDestination()}>
{this.props.author.name}
</Link>
{this.renderAuthorName()}
</span>
<span className="activity-row__message"> {i18n('ACTIVITY_' + this.props.type)} </span>
{_.includes(ticketRelatedTypes, this.props.type) ? this.renderTicketNumber() : this.props.to}
@ -76,6 +75,18 @@ class ActivityRow extends React.Component {
);
}
renderAuthorName() {
let name = this.props.author.name;
if (this.props.author.id) {
name = <Link className="activity-row__name-link" to={this.getNameLinkDestination()}>
{this.props.author.name}
</Link>;
}
return name;
}
renderTicketNumber() {
let ticketNumber = (this.props.mode === 'staff') ? this.props.ticketNumber : this.props.to;
@ -106,6 +117,7 @@ class ActivityRow extends React.Component {
'EDIT_SETTINGS': 'wrench',
'SIGNUP': 'user-plus',
'INVITE': 'user-plus',
'ADD_TOPIC': 'book',
'ADD_ARTICLE': 'book',
'DELETE_TOPIC': 'book',

View File

@ -107,18 +107,18 @@ class AdminPanelMenu extends React.Component {
getRoutes() {
const customLists = this.getCustomlists();
return this.getItemsByFilteredByLevel([
return this.getItemsByFilteredByLevel(_.without([
{
groupName: i18n('DASHBOARD'),
path: '/admin/panel',
icon: 'tachometer',
level: 1,
items: this.getItemsByFilteredByLevel([
{
/*{
name: i18n('STATISTICS'),
path: '/admin/panel/stats',
level: 1
},
},*/
{
name: i18n('LAST_ACTIVITY'),
path: '/admin/panel/activity',
@ -155,7 +155,7 @@ class AdminPanelMenu extends React.Component {
...customLists
])
},
{
this.props.config['user-system-enabled'] ? {
groupName: i18n('USERS'),
path: '/admin/panel/users',
icon: 'user',
@ -177,7 +177,7 @@ class AdminPanelMenu extends React.Component {
level: 1
}
])
},
} : null,
{
groupName: i18n('ARTICLES'),
path: '/admin/panel/articles',
@ -192,7 +192,6 @@ class AdminPanelMenu extends React.Component {
])
},
{
groupName: i18n('STAFF'),
path: '/admin/panel/staff',
icon: 'users',
@ -239,7 +238,7 @@ class AdminPanelMenu extends React.Component {
}
])
}
]);
], null));
}
getItemsByFilteredByLevel(items) {
@ -249,6 +248,7 @@ class AdminPanelMenu extends React.Component {
export default connect((store) => {
return {
level: store.session.userLevel
level: store.session.userLevel,
config: store.config
};
})(AdminPanelMenu);

View File

@ -67,7 +67,7 @@ class AddStaffModal extends React.Component {
return SessionStore.getDepartments().map(department => {
if(department.private*1){
return <spam>{department.name} <Icon name='user-secret'/> </spam>
}else {
} else {
return department.name;
}
});

View File

@ -11,7 +11,7 @@ import SessionStore from 'lib-app/session-store';
import PeopleList from 'app-components/people-list';
import ModalContainer from 'app-components/modal-container';
import AddStaffModal from 'app/admin/panel/staff/add-staff-modal';
import InviteStaffModal from 'app/admin/panel/staff/invite-staff-modal';
import Header from 'core-components/header';
import DropDown from 'core-components/drop-down';
@ -47,8 +47,8 @@ class AdminPanelStaffMembers extends React.Component {
<Header title={i18n('STAFF_MEMBERS')} description={i18n('STAFF_MEMBERS_DESCRIPTION')} />
<div className="admin-panel-staff-members__wrapper">
<DepartmentDropdown {...this.getDepartmentDropdownProps()} className="admin-panel-staff-members__dropdown" />
<Button onClick={this.onAddNewStaff.bind(this)} size="medium" type="secondary" className="admin-panel-staff-members__button">
<Icon name="user-plus" className=""/> {i18n('ADD_NEW_STAFF')}
<Button onClick={this.onInviteStaff.bind(this)} size="medium" type="secondary" className="admin-panel-staff-members__button">
<Icon name="user-plus" className=""/> {i18n('INVITE_STAFF')}
</Button>
</div>
{(this.props.loading) ? <Loading backgrounded /> : <PeopleList list={this.getStaffList()} page={this.state.page} onPageSelect={(index) => this.setState({page: index+1})} />}
@ -56,8 +56,8 @@ class AdminPanelStaffMembers extends React.Component {
);
}
onAddNewStaff() {
ModalContainer.openModal(<AddStaffModal onSuccess={this.retrieveStaffMembers.bind(this)} />);
onInviteStaff() {
ModalContainer.openModal(<InviteStaffModal onSuccess={this.retrieveStaffMembers.bind(this)} />);
}
getDepartmentDropdownProps() {

View File

@ -0,0 +1,120 @@
import React from 'react';
import _ from 'lodash';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import SessionStore from 'lib-app/session-store';
import Header from 'core-components/header'
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Button from 'core-components/button';
import Icon from 'core-components/icon';
class InviteStaffModal extends React.Component {
static contextTypes = {
closeModal: React.PropTypes.func
};
static propTypes = {
onSuccess: React.PropTypes.func
};
state = {
loading: false,
errors: {},
error: null
};
render() {
return (
<div className="invite-staff-modal">
<Header title={i18n('INVITE_STAFF')} description={i18n('INVITE_STAFF_DESCRIPTION')} />
<Form onSubmit={this.onSubmit.bind(this)} errors={this.getErrors()} onValidateErrors={errors => this.setState({errors})} loading={this.state.loading}>
<div className="row">
<div className="col-md-7">
<FormField name="name" label={i18n('NAME')} fieldProps={{size: 'large'}} validation="NAME" required />
<FormField name="email" label={i18n('EMAIL')} fieldProps={{size: 'large'}} validation="EMAIL" required />
<div className="invite-staff-modal__level-selector">
<FormField name="level" label={i18n('LEVEL')} field="select" fieldProps={{
items: [{content: i18n('LEVEL_1')}, {content: i18n('LEVEL_2')}, {content: i18n('LEVEL_3')}],
size: 'large'
}} />
</div>
</div>
<div className="col-md-5">
<div className="invite-staff-modal__departments">
<div className="invite-staff-modal__departments-title">{i18n('Departments')}</div>
<FormField name="departments" field="checkbox-group" fieldProps={{items: this.getDepartments()}} />
</div>
</div>
</div>
<SubmitButton type="secondary" size="small">
{i18n('SAVE')}
</SubmitButton>
<Button type="clean" onClick={this.onCancelClick.bind(this)}>
{i18n('CANCEL')}
</Button>
</Form>
</div>
);
}
getDepartments() {
return SessionStore.getDepartments().map(department => {
if(department.private*1){
return <span>{department.name} <Icon name='user-secret'/> </span>
} else {
return department.name;
}
});
}
onSubmit(form) {
let departments = _.filter(SessionStore.getDepartments(), (department, index) => {
return _.includes(form.departments, index);
}).map(department => department.id);
this.setState({loading: true});
API.call({
path: '/staff/invite',
data: {
name: form.name,
email: form.email,
level: form.level + 1,
departments: JSON.stringify(departments)
}
}).then(() => {
this.context.closeModal();
if(this.props.onSuccess) {
this.props.onSuccess();
}
}).catch((result) => {
this.setState({
loading: false,
error: result.message
});
});
}
onCancelClick(event) {
event.preventDefault();
this.context.closeModal();
}
getErrors() {
let errors = _.extend({}, this.state.errors);
if (this.state.error === 'ALREADY_A_STAFF') {
errors.email = i18n('EMAIL_EXISTS');
}
return errors;
}
}
export default InviteStaffModal;

View File

@ -0,0 +1,23 @@
@import "../../../../scss/vars";
.invite-staff-modal {
width: 700px;
&__level-selector {
text-align: center;
}
&__departments {
@include scrollbars();
border: 1px solid $grey;
padding: 20px;
height: 320px;
overflow-y: auto;
}
&__departments-title {
font-size: $font-size--md;
text-align: center;
}
}

View File

@ -1,4 +1,5 @@
import React from 'react';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
@ -12,10 +13,9 @@ import Button from 'core-components/button';
import Message from 'core-components/message';
import Icon from 'core-components/icon';
import ModalContainer from 'app-components/modal-container';
import MainSignUpWidget from 'app/main/main-signup/main-signup-widget';
import InviteUserWidget from 'app/admin/panel/users/invite-user-widget';
class AdminPanelListUsers extends React.Component {
state = {
loading: true,
users: [],
@ -39,11 +39,19 @@ class AdminPanelListUsers extends React.Component {
return (
<div className="admin-panel-list-users">
<Header title={i18n('LIST_USERS')} description={i18n('LIST_USERS_DESCRIPTION')} />
{(this.state.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_USERS')}</Message> : this.renderTableAndInviteButton()}
</div>
);
}
renderTableAndInviteButton() {
return (
<div>
<SearchBox className="admin-panel-list-users__search-box" placeholder={i18n('SEARCH_USERS')} onSearch={this.onSearch.bind(this)} />
{(this.state.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_USERS')}</Message> : <Table {...this.getTableProps()}/>}
<Table {...this.getTableProps()}/>
<div style={{textAlign: 'right', marginTop: 10}}>
<Button onClick={this.onCreateUser.bind(this)} type="secondary" size="medium">
<Icon size="sm" name="plus"/> {i18n('ADD_USER')}
<Button onClick={this.onInviteUser.bind(this)} type="secondary" size="medium">
<Icon size="sm" name="plus"/> {i18n('INVITE_USER')}
</Button>
</div>
</div>
@ -167,17 +175,17 @@ class AdminPanelListUsers extends React.Component {
}).catch(this.onUsersRejected.bind(this)).then(this.onUsersRetrieved.bind(this));
}
onCreateUser(user) {
onInviteUser(user) {
ModalContainer.openModal(
<div className="admin-panel-list-users__add-user-form">
<MainSignUpWidget onSuccess={this.onCreateUserSuccess.bind(this)} />
<div className="admin-panel-list-users__invite-user-form">
<InviteUserWidget onSuccess={this.onInviteUserSuccess.bind(this)} />
<div style={{textAlign: 'center'}}>
<Button onClick={ModalContainer.closeModal} type="link">{i18n('CLOSE')}</Button>
</div>
</div>
);
}
onCreateUserSuccess() {
onInviteUserSuccess() {
ModalContainer.closeModal();
}
@ -201,4 +209,8 @@ class AdminPanelListUsers extends React.Component {
}
}
export default AdminPanelListUsers;
export default connect((store) => {
return {
config: store.config
};
})(AdminPanelListUsers);

View File

@ -0,0 +1,165 @@
import React from 'react';
import ReactDOM from 'react-dom';
import _ from 'lodash';
import classNames from 'classnames';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import Captcha from 'app/main/captcha';
import SubmitButton from 'core-components/submit-button';
import Message from 'core-components/message';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import Widget from 'core-components/widget';
import Header from 'core-components/header';
class InviteUserWidget extends React.Component {
static propTypes = {
onSuccess: React.PropTypes.func,
className: React.PropTypes.string
};
constructor(props) {
super(props);
this.state = {
loading: false,
email: null,
customFields: []
};
}
componentDidMount() {
API.call({
path: '/system/get-custom-fields',
data: {}
})
.then(result => this.setState({customFields: result.data}));
}
render() {
return (
<Widget className={this.getClass()}>
<Header title={i18n('INVITE_USER')} description={i18n('INVITE_USER_VIEW_DESCRIPTION')} />
<Form {...this.getFormProps()}>
<div className="invite-user-widget__inputs">
<FormField {...this.getInputProps()} label={i18n('FULL_NAME')} name="name" validation="NAME" required/>
<FormField {...this.getInputProps()} label={i18n('EMAIL')} name="email" validation="EMAIL" required/>
{this.state.customFields.map(this.renderCustomField.bind(this))}
</div>
<div className="invite-user-widget__captcha">
<Captcha ref="captcha"/>
</div>
<SubmitButton type="primary">{i18n('INVITE_USER')}</SubmitButton>
</Form>
{this.renderMessage()}
</Widget>
);
}
renderCustomField(customField, key) {
if(customField.type === 'text') {
return (
<FormField {...this.getInputProps()}
name={`customfield_${customField.name}`}
key={key}
label={customField.name}
infoMessage={customField.description}
field="input"/>
);
} else {
const items = customField.options.map(option => ({content: option.name, value: option.name}));
return (
<FormField
name={`customfield_${customField.name}`}
key={key}
label={customField.name}
infoMessage={customField.description}
field="select"
fieldProps={{size:'medium', items}}/>
);
}
}
renderMessage() {
switch (this.state.message) {
case 'success':
return <Message type="success">{i18n('INVITE_USER_SUCCESS')}</Message>;
case 'fail':
return <Message type="error">{i18n('EMAIL_EXISTS')}</Message>;
default:
return null;
}
}
getClass() {
let classes = {
'invite-user-widget': true,
[this.props.className]: this.props.className
};
return classNames(classes);
}
getFormProps() {
return {
loading: this.state.loading,
className: 'invite-user-widget__form',
onSubmit: this.onInviteUserFormSubmit.bind(this)
};
}
getInputProps(password) {
return {
className: 'invite-user-widget__input',
fieldProps: {
size: 'medium',
password: password
}
};
}
onInviteUserFormSubmit(formState) {
const captcha = this.refs.captcha.getWrappedInstance();
if (!captcha.getValue()) {
captcha.focus();
} else {
this.setState({
loading: true
});
const form = _.clone(formState);
this.state.customFields.forEach(customField => {
if(customField.type === 'select') {
form[`customfield_${customField.name}`] = customField.options[form[`customfield_${customField.name}`]].name;
}
})
API.call({
path: '/user/invite',
data: _.extend({captcha: captcha.getValue()}, form)
}).then(this.onInviteUserSuccess.bind(this)).catch(this.onInviteUserFail.bind(this));
}
}
onInviteUserSuccess() {
this.setState({
loading: false,
message: 'success'
});
}
onInviteUserFail() {
this.setState({
loading: false,
message: 'fail'
});
}
}
export default InviteUserWidget;

View File

@ -0,0 +1,19 @@
.invite-user-widget {
padding: 30px;
text-align: center;
&__form {
margin-bottom: 20px;
}
&__inputs {
display: inline-block;
margin: 0 auto;
}
&__captcha {
margin: 10px auto 20px;
height: 78px;
width: 304px;
}
}

View File

@ -146,7 +146,7 @@ class CreateTicketForm extends React.Component {
message: 'success'
}, () => {
if(this.props.onSuccess) {
this.props.onSuccess();
this.props.onSuccess(result, email);
}
});
}

View File

@ -32,7 +32,7 @@ class DashboardCreateTicketPage extends React.Component {
);
}
onCreateTicketSuccess() {
onCreateTicketSuccess(result, email) {
if((this.props.location.pathname !== '/create-ticket')) {
setTimeout(() => {history.push('/dashboard')}, 2000);
} else {

View File

@ -30,7 +30,7 @@ class MainRecoverPasswordPage extends React.Component {
render() {
return (
<div className="main-recover-password-page">
<Widget title={i18n('RECOVER_PASSWORD')} className="col-md-4 col-md-offset-4">
<Widget title={this.props.location.query.invited ? i18n('SET_UP_PASSWORD') : i18n('RECOVER_PASSWORD')} className="col-md-4 col-md-offset-4">
<Form className="recover-password__form" onSubmit={this.onRecoverPasswordSubmit.bind(this)} loading={this.state.loading}>
<div className="recover-password__inputs">
<FormField placeholder={i18n('NEW_PASSWORD')} name="password" className="recover-password__input" validation="PASSWORD" fieldProps={{password: true}} required/>

View File

@ -1,7 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Widget from 'core-components/widget';
import MainSignUpWidget from 'app/main/main-signup/main-signup-widget';
class MainSignUpPage extends React.Component {
@ -9,70 +7,10 @@ class MainSignUpPage extends React.Component {
render() {
return (
<div className="main-signup-page">
<MainSignUpWidget {...this.props} className="col-md-6 col-md-offset-3" />
<MainSignUpWidget className="col-md-6 col-md-offset-3" />
</div>
);
}
renderMessage() {
switch (this.state.message) {
case 'success':
return <Message type="success">{i18n('SIGNUP_SUCCESS')}</Message>;
case 'fail':
return <Message type="error">{i18n('EMAIL_EXISTS')}</Message>;
default:
return null;
}
}
getFormProps() {
return {
loading: this.state.loading,
className: 'signup-widget__form',
onSubmit: this.onSignupFormSubmit.bind(this)
};
}
getInputProps(password) {
return {
className: 'signup-widget__input',
fieldProps: {
size: 'medium',
password: password
}
};
}
onSignupFormSubmit(formState) {
const captcha = this.refs.captcha.getWrappedInstance();
if (!captcha.getValue()) {
captcha.focus();
} else {
this.setState({
loading: true
});
API.call({
path: '/user/signup',
data: _.extend({captcha: captcha.getValue()}, formState)
}).then(this.onSignupSuccess.bind(this)).catch(this.onSignupFail.bind(this));
}
}
onSignupSuccess() {
this.setState({
loading: false,
message: 'success'
});
}
onSignupFail() {
this.setState({
loading: false,
message: 'fail'
});
}
}
export default MainSignUpPage;

View File

@ -1,10 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import _ from 'lodash';
import classNames from 'classnames';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import history from 'lib-app/history';
import Captcha from 'app/main/captcha';
import SubmitButton from 'core-components/submit-button';
@ -17,7 +17,6 @@ import Header from 'core-components/header';
class MainSignUpWidget extends React.Component {
static propTypes = {
onSuccess: React.PropTypes.func,
className: React.PropTypes.string
};
@ -153,6 +152,8 @@ class MainSignUpWidget extends React.Component {
this.setState({
loading: false,
message: 'success'
}, () => {
setTimeout(() => {history.push('/check-ticket')}, 2000);
});
}

View File

@ -300,7 +300,7 @@ export default {
'DELETE_USER_DESCRIPTION': 'O usuário não será capaz de entrar no sistema e todos os seus chamados serão apagados. Além disso, o e-mail não poderá mais ser usado.',
'DELETE_TOPIC_DESCRIPTION': 'Ao excluir o tópico, todos os artigos dele serão apagados.',
'EDIT_TOPIC_DESCRIPTION': 'Aqui você pode alterar o nome, o ícone ea cor do ícone do tópico.',
'ADD_ARTICLE_DESCRIPTION': 'Aqui você pode adicionar um artigo que estará disponível para cada usuário. Ele será adicionado dentro da categoria {categoria}.',
'ADD_ARTICLE_DESCRIPTION': 'Aqui você pode adicionar um artigo que estará disponível para cada usuário. Ele será adicionado dentro da categoria {category}.',
'LIST_ARTICLES_DESCRIPTION': 'Esta é uma lista de artigos que inclui informações sobre nossos serviços.',
'ADD_TOPIC_DESCRIPTION': 'Aqui você pode adicionar um tópico que funciona como uma categoria para artigos.',
'DELETE_ARTICLE_DESCRIPTION': 'Você vai excluir este artigo para sempre.',

View File

@ -272,7 +272,7 @@ export default {
'INSTALLATION_COMPLETED': 'Installation abgeschlossen.',
'INSTALLATION_COMPLETED_DESCRIPTION': 'Die Installation von OpenSupports ist abgeschlossen. Umleitung zum Admin-Panel...',
'STEP_TITLE': 'Schritt {aktuell} von {total} - {title}',
'STEP_TITLE': 'Schritt {current} von {total} - {title}',
'STEP_1_DESCRIPTION': 'Wählen Sie Ihre bevorzugte Sprache für den Installationsassistenten aus.',
'STEP_2_DESCRIPTION': 'Hier sind die Voraussetzungen für das Ausführen von OpenSupports aufgelistet. Bitte stellen Sie sicher, dass alle Anforderungen erfüllt sind.',
'STEP_3_DESCRIPTION': 'Bitte füllen Sie die MySQL-Datenbankkonfiguration aus.',

View File

@ -11,6 +11,7 @@ export default {
'SIGN_UP': 'Sign up',
'FORGOT_PASSWORD': 'Forgot your password?',
'RECOVER_PASSWORD': 'Recover Password',
'SET_UP_PASSWORD': 'Set up your password',
'RECOVER_SENT': 'An email with recover instructions has been sent.',
'NEW_EMAIL': 'New email',
'FULL_NAME': 'Full name',
@ -194,6 +195,8 @@ export default {
'NEVER': 'Never',
'HIMSELF': 'himself',
'ADD_USER': 'Add user',
'INVITE_USER': 'Invite user',
'INVITE_STAFF': 'Invite staff',
'UPLOAD_FILE': 'Upload file',
'PRIVATE': 'Private',
'ENABLE_USER': 'Enable User',
@ -233,6 +236,7 @@ export default {
'ACTIVITY_EDIT_SETTINGS': 'edited settings',
'ACTIVITY_SIGNUP': 'signed up',
'ACTIVITY_INVITE': 'invited user',
'ACTIVITY_ADD_TOPIC': 'added topic',
'ACTIVITY_ADD_ARTICLE': 'added article',
'ACTIVITY_DELETE_TOPIC': 'deleted topic',
@ -338,6 +342,8 @@ export default {
'IMAP_POLLING_DESCRIPTION': 'Inbox checking will not be done automatically by OpenSupports. You have to make POST requests periodically to this url to process the emails: {url}',
'NEW_CUSTOM_FIELD_DESCRIPTION': 'Here you can create a custom field for an user, it can be a blank text box or a fixed set of options.',
'CUSTOM_FIELDS_DESCRIPTION': 'Custom fields are defined additional fields the users are able to fill to provide more information about them.',
'INVITE_USER_VIEW_DESCRIPTION': 'Here you can invite an user to join our support system, he will just need to provide his password to create a new user.',
'INVITE_STAFF_DESCRIPTION': 'Here you can invite staff members to your teams.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid',
@ -374,6 +380,7 @@ export default {
//MESSAGES
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
'INVITE_USER_SUCCESS': 'You have invited a new user successfully in our support system',
'TICKET_SENT': 'Ticket has been created successfully.',
'VALID_RECOVER': 'Password recovered successfully',
'EMAIL_EXISTS': 'Email already exists',

View File

@ -206,7 +206,7 @@ export default {
'TYPE': 'Tipo',
'SELECT_INPUT': 'Seleccionar entrada',
'TEXT_INPUT': 'Entrada de texto',
'OPTION': 'Opción {índice}',
'OPTION': 'Opción {index}',
'OPTIONS': 'Opciones',
'FIELD_DESCRIPTION': 'Descripción del campo (opcional)',
'DESCRIPTION_ADD_CUSTOM_TAG': 'Aquí puedes agregar una nueva etiqueta personalizada',

View File

@ -62,7 +62,7 @@ export default {
'HIGH': 'Haute',
'MEDIUM': 'Moyenne',
'LOW': 'Faible',
'TITLE': 'Titre',
'TITLE': 'Objet',
'CONTENT': 'Contenu',
'SAVE': 'Enregistrer',
'DISCARD_CHANGES': 'Annuler les modifications',
@ -343,7 +343,7 @@ export default {
'ERROR_EMPTY': 'Valeur invalide',
'ERROR_PASSWORD': 'Mot de passe incorrect',
'ERROR_NAME': 'Nom incorrect',
'ERROR_TITLE': 'Titre incorrect',
'ERROR_TITLE': 'Objet incorrect',
'ERROR_EMAIL': 'Email invalide',
'ERROR_CONTENT_SHORT': 'Contenu trop court',
'PASSWORD_NOT_MATCH': 'Le mot de passe ne correspond pas',

View File

@ -300,7 +300,7 @@
'DELETE_USER_DESCRIPTION': 'Ο χρήστης δεν θα μπορέσει να συνδεθεί με τη γήρανση και όλα τα εισιτήρια του θα διαγραφούν. Επίσης, το ηλεκτρονικό ταχυδρομείο δεν μπορεί πλέον να χρησιμοποιηθεί.',
'DELETE_TOPIC_DESCRIPTION': 'Διαγράφοντας το θέμα, όλα τα άρθρα σε αυτό θα διαγραφούν.',
'EDIT_TOPIC_DESCRIPTION': 'Εδώ μπορείτε να αλλάξετε το όνομα, το εικονίδιο και το χρώμα του εικονιδίου του θέματος.',
'ADD_ARTICLE_DESCRIPTION': 'Εδώ μπορείτε να προσθέσετε ένα άρθρο που θα είναι διαθέσιμο για κάθε χρήστη. Θα προστεθεί μέσα στην κατηγορία {κατηγορία}.',
'ADD_ARTICLE_DESCRIPTION': 'Εδώ μπορείτε να προσθέσετε ένα άρθρο που θα είναι διαθέσιμο για κάθε χρήστη. Θα προστεθεί μέσα στην κατηγορία {category}.',
'LIST_ARTICLES_DESCRIPTION': 'Αυτή είναι μια λίστα με άρθρα που περιλαμβάνουν πληροφορίες σχετικά με τις υπηρεσίες μας.',
'ADD_TOPIC_DESCRIPTION': 'Εδώ μπορείτε να προσθέσετε ένα θέμα που λειτουργεί ως κατηγορία για άρθρα.',
'DELETE_ARTICLE_DESCRIPTION': 'Πρόκειται να διαγράψετε αυτό το άρθρο για πάντα.',

View File

@ -206,7 +206,7 @@ export default {
'TYPE': 'genere',
'SELECT_INPUT': 'Seleziona input',
'TEXT_INPUT': 'L\'immissione di testo',
'OPTION': 'Opzione {indice}',
'OPTION': 'Opzione {index}',
'OPTIONS': 'Opzioni',
'FIELD_DESCRIPTION': 'Descrizione del campo (facoltativo)',
'DESCRIPTION_ADD_CUSTOM_TAG': 'qui puoi aggiungere un nuovo tag personalizzato',

View File

@ -300,7 +300,7 @@ export default {
'DELETE_USER_DESCRIPTION': 'O usuário não será capaz de entrar no envelhecimento e todos os seus bilhetes serão apagados. Além disso, o e-mail não pode mais ser usado.',
'DELETE_TOPIC_DESCRIPTION': 'Ao excluir o tópico, todos os artigos dele serão apagados.',
'EDIT_TOPIC_DESCRIPTION': 'Aqui você pode alterar o nome, o ícone ea cor do ícone do tópico.',
'ADD_ARTICLE_DESCRIPTION': 'Aqui você pode adicionar um artigo que estará disponível para cada usuário. Ele será adicionado dentro da categoria {categoria}.',
'ADD_ARTICLE_DESCRIPTION': 'Aqui você pode adicionar um artigo que estará disponível para cada usuário. Ele será adicionado dentro da categoria {category}.',
'LIST_ARTICLES_DESCRIPTION': 'Esta é uma lista de artigos que inclui informações sobre nossos serviços.',
'ADD_TOPIC_DESCRIPTION': 'Aqui você pode adicionar um tópico que funciona como uma categoria para artigos.',
'DELETE_ARTICLE_DESCRIPTION': 'Você vai excluir este artigo para sempre.',

View File

@ -9,7 +9,7 @@ $systemControllerGroup->addController(new GetTicketStaffController);
$systemControllerGroup->addController(new GetNewTicketsStaffController);
$systemControllerGroup->addController(new GetAllTicketsStaffController);
$systemControllerGroup->addController(new SearchTicketStaffController);
$systemControllerGroup->addController(new AddStaffController);
$systemControllerGroup->addController(new InviteStaffController);
$systemControllerGroup->addController(new GetAllStaffController);
$systemControllerGroup->addController(new DeleteStaffController);
$systemControllerGroup->addController(new EditStaffController);

View File

@ -11,7 +11,7 @@ use Respect\Validation\Validator as DataValidator;
*
* @apiDescription This path retrieves information about all the staff member.
*
* @apiPermission staff3
* @apiPermission staff1
*
* @apiUse NO_PERMISSION
*

View File

@ -3,20 +3,19 @@ use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/add Add staff
* @api {post} /staff/invite Invite staff
* @apiVersion 4.5.0
*
* @apiName Add staff
* @apiName Invite staff
*
* @apiGroup Staff
*
* @apiDescription This path adds a new staff member.
* @apiDescription This path invites a new staff member.
*
* @apiPermission staff3
*
* @apiParam {String} name The name of the new staff member.
* @apiParam {String} email The email of the new staff member.
* @apiParam {String} password The password of the new staff member.
* @apiParam {Number} level The level of the new staff member.
* @apiParam {String} profilePic The profile pic of the new staff member.
* @apiParam {Number[]} departments The departments that will have assigned the new staff member.
@ -33,18 +32,16 @@ DataValidator::with('CustomValidations', true);
*
*/
class AddStaffController extends Controller {
const PATH = '/add';
class InviteStaffController extends Controller {
const PATH = '/invite';
const METHOD = 'POST';
private $name;
private $email;
private $password;
private $profilePic;
private $level;
private $departments;
public function validations() {
return [
'permission' => 'staff_3',
@ -57,53 +54,55 @@ class AddStaffController extends Controller {
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
],
'password' => [
'validation' => DataValidator::length(5, 200),
'error' => ERRORS::INVALID_PASSWORD
],
'level' => [
'validation' => DataValidator::between(1, 3, true),
'error' => ERRORS::INVALID_LEVEL
]
]
];
}
public function handler() {
$this->storeRequestData();
$staffRow = Staff::getDataStore($this->email, 'email');
if(!$staffRow->isNull()) throw new RequestException(ERRORS::ALREADY_A_STAFF);
$staff = new Staff();
$staff->setProperties([
'name'=> $this->name,
'email' => $this->email,
'password'=> Hashing::hashPassword(Hashing::generateRandomToken()),
'profilePic' => $this->profilePic,
'level' => $this->level,
'sharedDepartmentList' => $this->getDepartmentList()
]);
$staffRow = Staff::getDataStore($this->email,'email');
$this->addOwner();
if($staffRow->isNull()) {
$staff->setProperties([
'name'=> $this->name,
'email' => $this->email,
'password'=> Hashing::hashPassword($this->password),
'profilePic' => $this->profilePic,
'level' => $this->level,
'sharedDepartmentList' => $this->getDepartmentList()
$this->token = Hashing::generateRandomToken();
]);
$this->addOwner();
Log::createLog('ADD_STAFF', $this->name);
Response::respondSuccess([
'id' => $staff->store()
]);
return;
}
$recoverPassword = new RecoverPassword();
$recoverPassword->setProperties(array(
'email' => $this->email,
'token' => $this->token,
'staff' => true
));
$recoverPassword->store();
throw new RequestException(ERRORS::ALREADY_A_STAFF);
$this->sendInvitationMail();
Response::respondSuccess([
'id' => $staff->store()
]);
Log::createLog('INVITE', $this->name);
}
public function storeRequestData() {
$this->name = Controller::request('name');
$this->email = Controller::request('email');
$this->password = Controller::request('password');
$this->profilePic = Controller::request('profilePic');
$this->level = Controller::request('level');
$this->departments = Controller::request('departments');
@ -120,6 +119,7 @@ class AddStaffController extends Controller {
return $listDepartments;
}
public function addOwner() {
$departmentIds = json_decode($this->departments);
@ -129,4 +129,17 @@ class AddStaffController extends Controller {
$departmentRow->store();
}
}
public function sendInvitationMail() {
$mailSender = MailSender::getInstance();
$mailSender->setTemplate(MailTemplate::USER_INVITE, [
'to' => $this->email,
'name' => $this->name,
'url' => Setting::getSetting('url')->getValue(),
'token' => $this->token
]);
$mailSender->send();
}
}

View File

@ -37,9 +37,10 @@ class CommentController extends Controller {
private $ticket;
private $content;
private $session;
public function validations() {
$session = Session::getInstance();
$this->session = Session::getInstance();
if (Controller::isUserSystemEnabled() || Controller::isStaffLogged()) {
return [
@ -64,11 +65,11 @@ class CommentController extends Controller {
'error' => ERRORS::INVALID_CONTENT
],
'ticketNumber' => [
'validation' => DataValidator::equals($session->getTicketNumber()),
'validation' => DataValidator::equals($this->session->getTicketNumber()),
'error' => ERRORS::INVALID_TICKET
],
'csrf_token' => [
'validation' => DataValidator::equals($session->getToken()),
'validation' => DataValidator::equals($this->session->getToken()),
'error' => ERRORS::INVALID_TOKEN
]
]
@ -79,28 +80,29 @@ class CommentController extends Controller {
public function handler() {
$this->requestData();
$ticketAuthor = $this->ticket->authorToArray();
$isAuthor = $this->ticket->isAuthor(Controller::getLoggedUser()) || Session::getInstance()->isTicketSession();
$isOwner = $this->ticket->isOwner(Controller::getLoggedUser());
$user = Controller::getLoggedUser();
$isAuthor = $this->session->isTicketSession() || $this->ticket->isAuthor($this->user);
$isOwner = $this->ticket->isOwner($this->user);
$private = Controller::request('private');
if(!Controller::isStaffLogged() && Controller::isUserSystemEnabled() && !$isAuthor){
throw new RequestException(ERRORS::NO_PERMISSION);
}
if(!$user->canManageTicket($this->ticket)) {
if(!$this->session->isTicketSession() && !$this->user->canManageTicket($this->ticket)) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
$this->storeComment();
if($isAuthor && $this->ticket->owner) {
if(!$isAuthor && !$private) {
$this->sendMail($ticketAuthor);
}
if($this->ticket->owner && !$isOwner) {
$this->sendMail([
'email' => $this->ticket->owner->email,
'name' => $this->ticket->owner->name,
'staff' => true
]);
} else if($isOwner) {
!Controller::request('private') ? $this->sendMail($ticketAuthor) : null;
}
Log::createLog('COMMENT', $this->ticket->ticketNumber);
@ -112,6 +114,7 @@ class CommentController extends Controller {
$ticketNumber = Controller::request('ticketNumber');
$this->ticket = Ticket::getByTicketNumber($ticketNumber);
$this->content = Controller::request('content', true);
$this->user = Controller::getLoggedUser();
}
private function storeComment() {
@ -129,12 +132,14 @@ class CommentController extends Controller {
));
if(Controller::isStaffLogged()) {
$this->ticket->unread = !$this->ticket->isAuthor(Controller::getLoggedUser());
$this->ticket->unreadStaff = !$this->ticket->isOwner(Controller::getLoggedUser());
$comment->authorStaff = Controller::getLoggedUser();
$this->ticket->unread = !$this->ticket->isAuthor($this->user);
$this->ticket->unreadStaff = !$this->ticket->isOwner($this->user);
$comment->authorStaff = $this->user;
} else if(Controller::isUserSystemEnabled()) {
$this->ticket->unreadStaff = true;
$comment->authorUser = Controller::getLoggedUser();
$comment->authorUser = $this->user;
} else {
$this->ticket->unreadStaff = true;
}
$this->ticket->addEvent($comment);

View File

@ -115,10 +115,16 @@ class CreateController extends Controller {
}
}
Log::createLog('CREATE_TICKET', $this->ticketNumber);
Response::respondSuccess([
'ticketNumber' => $this->ticketNumber
]);
if(!Controller::isUserSystemEnabled() && !Controller::isStaffLogged()) {
$session = Session::getInstance();
$session->createTicketSession($this->ticketNumber);
}
Log::createLog('CREATE_TICKET', $this->ticketNumber);
}
private function storeTicket() {

View File

@ -4,6 +4,7 @@ $userControllers->setGroupPath('/user');
$userControllers->addController(new LoginController);
$userControllers->addController(new SignUpController);
$userControllers->addController(new InviteUserController);
$userControllers->addController(new LogoutController);
$userControllers->addController(new CheckSessionController);
$userControllers->addController(new SendRecoverPasswordController);

View File

@ -0,0 +1,140 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /user/invite Invite
* @apiVersion 4.5.0
*
* @apiName Invite
*
* @apiGroup User
*
* @apiDescription This path invites an user on the system.
*
* @apiPermission staff1
*
* @apiParam {String} name The name of the invited user.
* @apiParam {String} email The email of the invited user.
* @apiParam {String} customfield_ Custom field values for this user.
*
* @apiUse INVALID_NAME
* @apiUse INVALID_EMAIL
* @apiUse INVALID_CAPTCHA
* @apiUse USER_EXISTS
* @apiUse ALREADY_BANNED
* @apiUse NO_PERMISSION
* @apiUse INVALID_CUSTOM_FIELD_OPTION
*
* @apiSuccess {Object} data Information about invited user
* @apiSuccess {Number} data.userId Id of the invited user
* @apiSuccess {String} data.userEmail Email of the invited user
*
*/
class InviteUserController extends Controller {
const PATH = '/invite';
const METHOD = 'POST';
private $userEmail;
private $userName;
public function validations() {
$validations = [
'permission' => 'staff_1',
'requestData' => [
'name' => [
'validation' => DataValidator::length(2, 55),
'error' => ERRORS::INVALID_NAME
],
'email' => [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
]
]
];
$validations['requestData']['captcha'] = [
'validation' => DataValidator::captcha(),
'error' => ERRORS::INVALID_CAPTCHA
];
return $validations;
}
public function handler() {
if (!Controller::isUserSystemEnabled()) {
throw new RequestException(ERRORS::USER_SYSTEM_DISABLED);
}
$this->storeRequestData();
$existentUser = User::getUser($this->userEmail, 'email');
if (!$existentUser->isNull()) {
throw new RequestException(ERRORS::USER_EXISTS);
}
$banRow = Ban::getDataStore($this->userEmail, 'email');
if (!$banRow->isNull()) {
throw new RequestException(ERRORS::ALREADY_BANNED);
}
$userId = $this->createNewUserAndRetrieveId();
$this->token = Hashing::generateRandomToken();
$recoverPassword = new RecoverPassword();
$recoverPassword->setProperties(array(
'email' => $this->userEmail,
'token' => $this->token,
'staff' => false
));
$recoverPassword->store();
$this->sendInvitationMail();
Response::respondSuccess([
'userId' => $userId,
'userEmail' => $this->userEmail
]);
Log::createLog('INVITE', $this->userName);
}
public function storeRequestData() {
$this->userName = Controller::request('name');
$this->userEmail = Controller::request('email');
}
public function createNewUserAndRetrieveId() {
$userInstance = new User();
$userInstance->setProperties([
'name' => $this->userName,
'signupDate' => Date::getCurrentDate(),
'tickets' => 0,
'email' => $this->userEmail,
'password' => Hashing::hashPassword(Hashing::generateRandomToken()),
'verificationToken' => null,
'xownCustomfieldvalueList' => $this->getCustomFieldValues()
]);
return $userInstance->store();
}
public function sendInvitationMail() {
$mailSender = MailSender::getInstance();
$mailSender->setTemplate(MailTemplate::USER_INVITE, [
'to' => $this->userEmail,
'name' => $this->userName,
'url' => Setting::getSetting('url')->getValue(),
'token' => $this->token
]);
$mailSender->send();
}
}

View File

@ -56,10 +56,6 @@ class RecoverPasswordController extends Controller {
}
public function handler() {
if(!Controller::isUserSystemEnabled()) {
throw new RequestException(ERRORS::USER_SYSTEM_DISABLED);
}
$this->requestData();
$this->changePassword();
}
@ -69,30 +65,38 @@ class RecoverPasswordController extends Controller {
$this->token = Controller::request('token');
$this->password = Controller::request('password');
}
public function changePassword() {
$recoverPassword = RecoverPassword::getDataStore($this->token, 'token');
if($recoverPassword->isNull() || $recoverPassword->email !== $this->email) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if(!Controller::isUserSystemEnabled() && !$recoverPassword->staff) {
throw new RequestException(ERRORS::USER_SYSTEM_DISABLED);
}
if($recoverPassword->staff) {
$this->user = Staff::getDataStore($this->email, 'email');
}else {
} else {
$this->user = User::getDataStore($this->email, 'email');
}
if (!$recoverPassword->isNull() && !$this->user->isNull()) {
$recoverPassword->delete();
if($this->user->isNull()) throw new RequestException(ERRORS::NO_PERMISSION);
$this->user->setProperties([
'password' => Hashing::hashPassword($this->password)
]);
$recoverPassword->delete();
$this->user->store();
$this->user->setProperties([
'password' => Hashing::hashPassword($this->password)
]);
$this->sendMail();
Response::respondSuccess(['staff' => $recoverPassword->staff]);
} else {
throw new RequestException(ERRORS::NO_PERMISSION);
}
$this->user->store();
$this->sendMail();
Response::respondSuccess(['staff' => $recoverPassword->staff]);
}
public function sendMail() {
$mailSender = MailSender::getInstance();

View File

@ -49,17 +49,18 @@ class SendRecoverPasswordController extends Controller {
}
public function handler() {
if(!Controller::isUserSystemEnabled()) {
$this->staff = Controller::request('staff');
if(!Controller::isUserSystemEnabled() && !$this->staff) {
throw new RequestException(ERRORS::USER_SYSTEM_DISABLED);
}
$this->staff = Controller::request('staff');
$email = Controller::request('email');
if($this->staff){
$this->user = Staff::getUser($email,'email');
}else {
$this->user = User::getUser($email,'email');
$this->user = Staff::getUser($email, 'email');
} else {
$this->user = User::getUser($email, 'email');
}
if(!$this->user->isNull()) {

View File

@ -18,7 +18,7 @@ DataValidator::with('CustomValidations', true);
* @apiParam {String} name The name of the new user.
* @apiParam {String} email The email of the new user.
* @apiParam {String} password The password of the new user.
* @apiParam {String} apiKey APIKey to sign up an user if the user system is disabled.
* @apiParam {String} apiKey APIKey to sign up an user if the registration system is disabled.
* @apiParam {String} customfield_ Custom field values for this user.
*
* @apiUse INVALID_NAME

View File

@ -25,6 +25,12 @@ class MailTexts {
'Hi, {{name}}. You have requested to recover your password.',
'Use this code in {{url}}/recover-password?email={{to}}&token={{token}} or click the button below.',
],
'USER_INVITE' => [
'You have been invited - OpenSupports',
'You have been invited',
'Hi, {{name}}. You have been invited to join our support center.',
'Use this code in {{url}}/recover-password?email={{to}}&token={{token}}&invited=true or click the button below to set up your password.'
],
'USER_SYSTEM_DISABLED' => [
'Access system changed - OpenSupports',
'Access system changed',
@ -85,6 +91,12 @@ class MailTexts {
'喂 {{name}}。 您已要求恢复密码。',
'使用此代码 {{url}}/recover-password?email={{to}}&token={{token}} 或单击下面的按钮.',
],
'USER_INVITE' => [
'您已受邀 - OpenSupports',
'您已受邀',
'你好, {{name}}. 邀请您加入我们的支持中心.',
'使用此代码 {{url}}/recover-password?email={{to}}&token={{token}}&invited=true 或单击下面的按钮来设置密码.'
],
'USER_SYSTEM_DISABLED' => [
'访问系统更改 - OpenSupports',
'访问系统更改',
@ -145,6 +157,12 @@ class MailTexts {
'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.',
],
'USER_INVITE' => [
'Du bist eingeladen - OpenSupports',
'Du bist eingeladen',
'Hallo, {{name}}. Sie wurden zu unserem Support-Center eingeladen.',
'Verwenden Sie diesen Code in {{url}}/recover-password?email={{to}}&token={{token}}&invited=true oder klicken Sie auf die Schaltfläche unten, um Ihr Passwort einzurichten.'
],
'USER_SYSTEM_DISABLED' => [
'Access system changed - OpenSupports',
'Zugriffssystem geändert',
@ -205,6 +223,12 @@ class MailTexts {
'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.',
],
'USER_INVITE' => [
'Haz sido invitado - OpenSupports',
'Haz sido invitado',
'Hola, {{name}}. Haz sido invitado a unirte a nuestro sistema de soporte.',
'Usa este código en {{url}}/recover-password?email={{to}}&token={{token}}&invited=true o haz click en el botón de abajo para establecer tu contraseña.'
],
'USER_SYSTEM_DISABLED' => [
'Sistema de acceso cambiado - OpenSupports',
'Sistema de acceso cambiado',
@ -265,6 +289,12 @@ class MailTexts {
'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.',
],
'USER_INVITE' => [
'You have been invited - OpenSupports',
'You have been invited',
'Hi, {{name}}. You have been invited to join our support center.',
'Use this code in {{url}}/recover-password?email={{to}}&token={{token}}&invited=true or click the button below to set up your password.'
],
'USER_SYSTEM_DISABLED' => [
'Système d\'accès modifié - OpenSupports',
'Système d\'accès modifié',
@ -325,6 +355,12 @@ class MailTexts {
'नमस्ते {{name}}. आपने अपना पासवर्ड पुनर्प्राप्त करने का अनुरोध किया है',
'इस कोड का उपयोग करें {{url}}/recover-password?email={{to}}&token={{token}} या नीचे दिए गए बटन पर क्लिक करें.',
],
'USER_INVITE' => [
'आपको आमंत्रित किया गया है - OpenSupports',
'आपको आमंत्रित किया गया है',
'नमस्ते, {{name}}. आपको हमारे सहायता केंद्र से जुड़ने के लिए आमंत्रित किया गया है.',
'इस कोड का उपयोग करें {{url}}/recover-password?email={{to}}&token={{token}}&invited=true या अपना पासवर्ड सेट करने के लिए नीचे दिए गए बटन पर क्लिक करें.'
],
'USER_SYSTEM_DISABLED' => [
'sistem akses berubah - OpenSupports',
'एक्सेस सिस्टम बदल गया',
@ -385,6 +421,12 @@ class MailTexts {
'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.',
],
'USER_INVITE' => [
'Sei stato invitato - OpenSupports',
'Sei stato invitato',
'Ciao, {{name}}. Sei stato invitato a far parte del nostro centro di supporto.',
'Usa questo codice in {{url}}/recover-password?email={{to}}&token={{token}}&invited=true oppure fai clic sul pulsante in basso per impostare la password.'
],
'USER_SYSTEM_DISABLED' => [
'Il sistema di accesso è cambiato - OpenSupports',
'Modifica sistema di accesso',
@ -445,6 +487,12 @@ class MailTexts {
'こんにちは、{{name}}。 パスワードの回復を要求しました。',
'でこのコードを使用 {{url}}/recover-password?email={{to}}&token={{token}} 下のボタンをクリックしてください.',
],
'USER_INVITE' => [
'招待されました - OpenSupports',
'招待されました',
'こんにちは, {{name}}. サポートセンターに招待されました.',
'このコードを {{url}}/recover-password?email={{to}}&token={{token}}&invited=true または、下のボタンをクリックしてパスワードを設定します.'
],
'USER_SYSTEM_DISABLED' => [
'アクセスシステムが変更されました - OpenSupports',
'アクセスシステムが変更されました',
@ -505,6 +553,12 @@ class MailTexts {
'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.',
],
'USER_INVITE' => [
'Você foi convidado - OpenSupports',
'Você foi convidado',
'Oi, {{name}}. Você foi convidado a participar do nosso centro de suporte.',
'Use este código em {{url}}/recover-password?email={{to}}&token={{token}}&invited=true ou clique no botão abaixo para configurar sua senha.'
],
'USER_SYSTEM_DISABLED' => [
'Sistema de acesso alterado - OpenSupports',
'Sistema de acesso alterado',
@ -565,6 +619,12 @@ class MailTexts {
'Здравствуй, {{name}}. Вы запросили восстановить пароль.',
'Используйте этот код в {{url}}/recover-password?email={{to}}&token={{token}} или нажмите кнопку ниже.',
],
'USER_INVITE' => [
'Вы были приглашены - OpenSupports',
'Вы были приглашены',
'Здравствуй, {{name}}. Вас пригласили присоединиться к нашему центру поддержки.',
'Используйте этот код в {{url}}/recover-password?email={{to}}&token={{token}}&invited=true или нажмите кнопку ниже, чтобы установить пароль.'
],
'USER_SYSTEM_DISABLED' => [
'Система доступа изменена - OpenSupports',
'Система доступа изменена',
@ -625,6 +685,12 @@ class MailTexts {
'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.',
],
'USER_INVITE' => [
'Davet edildin - OpenSupports',
'Davet edildin',
'Merhaba, {{name}}. Destek merkezimize katılmaya davet edildiniz.',
'Bu kodu {{url}}/recover-password?email={{to}}&token={{token}}&invited=true veya şifrenizi ayarlamak için aşağıdaki butona tıklayın.'
],
'USER_SYSTEM_DISABLED' => [
'Erişim sistemi değiştirildi - OpenSupports',
'Erişim sistemi değiştirildi',
@ -685,6 +751,12 @@ class MailTexts {
'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.',
],
'USER_INVITE' => [
'Você foi convidado - OpenSupports',
'Você foi convidado',
'Oi, {{name}}. Você foi convidado a participar do nosso centro de suporte.',
'Use este código em {{url}}/recover-password?email={{to}}&token={{token}}&invited=true ou clique no botão abaixo para configurar sua senha.'
],
'USER_SYSTEM_DISABLED' => [
'Sistema de acesso alterado - OpenSupports',
'Sistema de acesso alterado',
@ -745,6 +817,12 @@ class MailTexts {
'Γεια σου, {{name}}. Ζητήσατε να ανακτήσετε τον κωδικό πρόσβασής σας.',
'Χρησιμοποιήστε αυτόν τον κωδικό στο {{url}} / recover-password? Email = {{to}} & token = {{token}} ή κάντε κλικ στο παρακάτω κουμπί.',
],
'USER_INVITE' => [
'Έχετε προσκληθεί - OpenSupports',
'Έχετε προσκληθεί',
'Γεια σου, {{name}}. Έχετε προσκληθεί να συμμετάσχετε στο κέντρο υποστήριξής μας.',
'Χρησιμοποιήστε αυτόν τον κωδικό στο {{url}}/recover-password?email={{to}}&token={{token}}&invited=true ή κάντε κλικ στο παρακάτω κουμπί για να ρυθμίσετε τον κωδικό πρόσβασής σας.'
],
'USER_SYSTEM_DISABLED' => [
'Το σύστημα πρόσβασης άλλαξε - OpenSupports',
'Το σύστημα πρόσβασης άλλαξε',
@ -805,6 +883,12 @@ class MailTexts {
'Hallo, {{name}}. U heeft een verzoek gedaan om uw wachtwoord te resetten.',
'Gebruik deze code {{url}}/recover-password?email={{to}}&token={{token}} of klik op de knop hieronder.'
],
'USER_INVITE' => [
'Je bent uitgenodigd - OpenSupports',
'Je bent uitgenodigd',
'Hallo, {{name}}. U bent uitgenodigd om lid te worden van ons ondersteuningscentrum.',
'Gebruik deze code in {{url}}/recover-password?email={{to}}&token={{token}}&invited=true of klik op de onderstaande knop om uw wachtwoord in te stellen.'
],
'USER_SYSTEM_DISABLED' => [
'Toegangssysteem gewijzigd - OpenSupports',
'Toegang tot incidenten is gewijzigd',
@ -865,6 +949,12 @@ class MailTexts {
'Hej, {{name}}. Zażądałeś odzyskania hasła.',
'Użyj tego linka {{url}}/recover-password?email={{to}}&token={{token}} lub kliknij przycisk poniżej.',
],
'USER_INVITE' => [
'Zostałeś zaproszony - OpenSupports',
'Zostałeś zaproszony',
'Hej, {{name}}. Zaproszono Cię do dołączenia do naszego centrum wsparcia.',
'Użyj tego kodu w {{url}}/recover-password?email={{to}}&token={{token}}&invited=true lub kliknij przycisk poniżej, aby ustawić hasło.'
],
'USER_SYSTEM_DISABLED' => [
'Zmieniono dostęp do systemu - OpenSupports',
'Zmieniono dostęp do systemu',

View File

@ -0,0 +1,384 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Support Center</title>
<style type="text/css">
/* Take care of image borders and formatting, client hacks */
img { max-width: 600px; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic;}
a img { border: none; }
table { border-collapse: collapse !important;}
#outlook a { padding:0; }
.ReadMsgBody { width: 100%; }
.ExternalClass { width: 100%; }
.backgroundTable { margin: 0 auto; padding: 0; width: 100% !important; }
table td { border-collapse: collapse; }
.ExternalClass * { line-height: 115%; }
.container-for-gmail-android { min-width: 600px; }
/* General styling */
* {
font-family: Helvetica, Arial, sans-serif;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
margin: 0 !important;
height: 100%;
color: #676767;
}
td {
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
color: #777777;
text-align: center;
line-height: 21px;
}
a {
color: #676767;
text-decoration: none !important;
}
.pull-left {
text-align: left;
}
.pull-right {
text-align: right;
}
.header-lg,
.header-md,
.header-sm {
font-size: 32px;
font-weight: 700;
line-height: normal;
padding: 35px 0 0;
color: #4d4d4d;
}
.header-md {
font-size: 24px;
}
.header-sm {
padding: 5px 0;
font-size: 18px;
line-height: 1.3;
}
.content-padding {
padding: 20px 0 30px;
}
.mobile-header-padding-right {
width: 290px;
text-align: right;
padding-left: 10px;
}
.mobile-header-padding-left {
width: 290px;
text-align: left;
padding-left: 10px;
}
.free-text {
width: 100% !important;
padding: 10px 60px 0px;
}
.block-rounded {
border-radius: 5px;
border: 1px solid #e5e5e5;
vertical-align: top;
}
.button {
padding: 55px 0 0;
}
.info-block {
padding: 0 20px;
width: 260px;
}
.mini-block-container {
padding: 30px 50px;
width: 500px;
}
.mini-block {
background-color: #ffffff;
width: 498px;
border: 1px solid #cccccc;
border-radius: 5px;
padding: 60px 75px;
}
.block-rounded {
width: 260px;
}
.info-img {
width: 258px;
border-radius: 5px 5px 0 0;
}
.force-width-img {
width: 480px;
height: 1px !important;
}
.force-width-full {
width: 600px;
height: 1px !important;
}
.user-img img {
width: 82px;
border-radius: 5px;
border: 1px solid #cccccc;
}
.user-img {
width: 92px;
text-align: left;
}
.user-msg {
width: 236px;
font-size: 14px;
text-align: left;
font-style: italic;
}
.code-block {
padding: 10px 0;
border: 1px solid #cccccc;
color: #4d4d4d;
font-weight: bold;
font-size: 17px;
text-align: center;
}
.force-width-gmail {
min-width:600px;
height: 0px !important;
line-height: 1px !important;
font-size: 1px !important;
}
.button-width {
width: 228px;
}
</style>
<style type="text/css" media="screen">
@import url(http://fonts.googleapis.com/css?family=Oxygen:400,700);
</style>
<style type="text/css" media="screen">
@media screen {
/* Thanks Outlook 2013! */
* {
font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;
}
}
</style>
<style type="text/css" media="only screen and (max-width: 480px)">
/* Mobile styles */
@media only screen and (max-width: 480px) {
table[class*="container-for-gmail-android"] {
min-width: 290px !important;
width: 100% !important;
}
table[class="w320"] {
width: 320px !important;
}
img[class="force-width-gmail"] {
display: none !important;
width: 0 !important;
height: 0 !important;
}
a[class="button-width"],
a[class="button-mobile"] {
width: 248px !important;
}
td[class*="mobile-header-padding-left"] {
width: 160px !important;
padding-left: 0 !important;
}
td[class*="mobile-header-padding-right"] {
width: 160px !important;
padding-right: 0 !important;
}
td[class="header-lg"] {
font-size: 24px !important;
padding-bottom: 5px !important;
}
td[class="header-md"] {
font-size: 18px !important;
padding-bottom: 5px !important;
}
td[class="content-padding"] {
padding: 5px 0 30px !important;
}
td[class="button"] {
padding: 15px 0 5px !important;
}
td[class*="free-text"] {
padding: 10px 18px 30px !important;
}
img[class="force-width-img"],
img[class="force-width-full"] {
display: none !important;
}
td[class="info-block"] {
display: block !important;
width: 280px !important;
padding-bottom: 40px !important;
}
td[class="info-img"],
img[class="info-img"] {
width: 278px !important;
}
td[class="mini-block-container"] {
padding: 8px 20px !important;
width: 280px !important;
}
td[class="mini-block"] {
padding: 20px !important;
}
td[class="user-img"] {
display: block !important;
text-align: center !important;
width: 100% !important;
padding-bottom: 10px;
}
td[class="user-msg"] {
display: block !important;
padding-bottom: 20px !important;
}
}
</style>
</head>
<body bgcolor="#f7f7f7">
<table align="center" cellpadding="0" cellspacing="0" class="container-for-gmail-android" width="100%">
<tr>
<td align="left" valign="top" width="100%" style="background-color: #ffffff;">
<center>
<table cellspacing="0" cellpadding="0" width="100%" bgcolor="#ffffff" style="border-bottom: 1px solid #cccccc">
<tr>
<td width="100%" height="80" valign="top" style="text-align: center; vertical-align:middle;">
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;padding: 15px 0;">
<img src="{{IMAGE_HEADER_URL}}" alt="logo">
</td>
</tr>
</table>
</center>
<!--[if gte mso 9]>
</v:textbox>
</v:rect>
<![endif]-->
</td>
</tr>
</table>
</center>
</td>
</tr>
<tr>
<td align="center" valign="top" width="100%" style="background-color: #f7f7f7;" class="content-padding">
<center>
<table cellspacing="0" cellpadding="0" width="600" class="w320">
<tr>
<td class="header-lg">
{{USER_INVITE_MATCH_1}}
</td>
</tr>
<tr>
<td class="free-text">
{{USER_INVITE_MATCH_2}}
</td>
</tr>
<tr>
<td class="mini-block-container">
<table cellspacing="0" cellpadding="0" width="100%" style="border-collapse:separate !important;">
<tr>
<td class="mini-block">
<table cellpadding="0" cellspacing="0" width="100%">
<tr>
<td style="padding-bottom: 30px;">
{{USER_INVITE_MATCH_3}}
</td>
</tr>
<tr>
<td class="code-block">
{{token}}
</td>
</tr>
<tr>
<td class="button">
<div><a class="button-mobile" target="_blank" href="{{url}}/recover-password?email={{to}}&token={{token}}&invited=true"
style="background-color:#ff6f6f;border-radius:5px;color:#ffffff;display:inline-block;font-family:'Cabin', Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;line-height:45px;text-align:center;text-decoration:none;width:155px;-webkit-text-size-adjust:none;mso-hide:all;">Set up your password</a></div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</center>
</td>
</tr>
<tr>
<td align="center" valign="top" width="100%" style="background-color: #ffffff; border-top: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5; height: 100px;">
<center>
<table cellspacing="0" cellpadding="0" width="600" class="w320">
<tr>
<td style="padding: 25px 0 25px">
<strong>OpenSupports</strong><br />
Open source ticket system<br />
www.opensupports.com<br /><br />
</td>
</tr>
</table>
</center>
</td>
</tr>
</table>
</body>
</html>

View File

@ -22,21 +22,36 @@ class Log extends DataStore {
'authorUser',
'authorStaff',
'to',
'date'
'date',
'authorName'
];
}
public static function createLog($type,$to, $author = null) {
public static function createLog($type, $to, $author = null) {
$session = Session::getInstance();
$authorName = '';
if($session->isTicketSession()) {
$ticketNumber = $session->getTicketNumber();
$ticket = Ticket::getByTicketNumber($ticketNumber);
$authorName = $ticket->authorToArray()['name'];
}
if($author === null) {
$author = Controller::getLoggedUser();
}
if(!$author->isNull()) {
$authorName = $author->name;
}
$log = new Log();
$log->setProperties(array(
'type' => $type,
'to' => $to,
'date' => Date::getCurrentDate()
'date' => Date::getCurrentDate(),
'authorName' => $authorName
));
if($author instanceof User) {
@ -55,8 +70,8 @@ class Log extends DataStore {
'type' => $this->type,
'to' => $this->to,
'author' => [
'name' => $author->name,
'id' => $author->id,
'name' => $this->authorName,
'id' => ($author && !$author->isNull()) ? $author->id : null,
'staff' => $author instanceof Staff
],
'date' => $this->date

View File

@ -19,6 +19,7 @@ class MailTemplate extends DataStore {
const USER_SIGNUP = 'USER_SIGNUP';
const USER_PASSWORD = 'USER_PASSWORD';
const PASSWORD_FORGOT = 'PASSWORD_FORGOT';
const USER_INVITE = 'USER_INVITE';
const USER_SYSTEM_DISABLED = 'USER_SYSTEM_DISABLED';
const USER_SYSTEM_ENABLED = 'USER_SYSTEM_ENABLED';
const TICKET_CREATED = 'TICKET_CREATED';
@ -32,6 +33,7 @@ class MailTemplate extends DataStore {
'USER_PASSWORD' => 'data/mail-templates/user-edit-password.html',
'USER_EMAIL' => 'data/mail-templates/user-edit-email.html',
'PASSWORD_FORGOT' => 'data/mail-templates/user-password-forgot.html',
'USER_INVITE' => 'data/mail-templates/user-invite.html',
'USER_SYSTEM_DISABLED' => 'data/mail-templates/user-system-disabled.html',
'USER_SYSTEM_ENABLED' => 'data/mail-templates/user-system-enabled.html',
'TICKET_CREATED' => 'data/mail-templates/ticket-created.html',

View File

@ -83,15 +83,16 @@ class Ticketevent extends DataStore {
public function toArray() {
$user = ($this->authorStaff) ? $this->authorStaff : $this->authorUser;
$author = $this->ticket->authorToArray();
return [
'type' => $this->type,
'ticketNumber' => $this->ticket->ticketNumber,
'author' => [
'name' => $user ? $user->name : null,
'name' => $user ? $user->name : $author['name'],
'staff' => $user instanceOf Staff,
'id' => $user ? $user->id : null,
'customfields' => $user->xownCustomfieldvalueList ? $user->xownCustomfieldvalueList->toArray() : [],
'customfields' => ($user && $user->xownCustomfieldvalueList) ? $user->xownCustomfieldvalueList->toArray() : [],
],
'edited' => $this->editedContent
];

View File

@ -33,7 +33,7 @@ require './ticket/change-department.rb'
require './ticket/close.rb'
require './ticket/re-open.rb'
require './ticket/delete.rb'
require './staff/add.rb'
require './staff/invite.rb'
require './staff/get.rb'
require './staff/edit.rb'
require './staff/delete.rb'

View File

@ -16,25 +16,33 @@ class Scripts
})
end
def self.createStaff(email, password, name, level='1')
def self.createStaff(email, password, name, level='1') # WARNING: NOT USED ANYWHERE
departments = request('/system/get-settings', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})['data']['departments']
departments = departments.collect { |x| x.id }
response = request('/staff/add', {
response = request('/staff/invite', {
:name => name,
:email => email,
:password => password,
:level => level,
:departments => departments.to_string
})
recoverpassword = $database.getRow('recoverpassword', email, 'email')
response = request('/user/recover-password', {
email: email,
password: password,
token: recoverpassword['token']
})
if response['status'] === 'fail'
raise response['message']
end
end
def self.deleteStaff(staffId)
response = request('/staff/delete', {
staffId: staffId,
@ -106,6 +114,7 @@ class Scripts
description: description
})
end
def self.createTag(name, color)
request('/ticket/create-tag', {
csrf_userid: $csrf_userid,
@ -114,6 +123,7 @@ class Scripts
color: color
})
end
def self.assignTicket(ticketnumber)
request('/staff/assign-ticket', {
ticketNumber: ticketnumber,
@ -121,6 +131,7 @@ class Scripts
csrf_token: $csrf_token
})
end
def self.commentTicket(ticketnumber,content)
request('/ticket/comment', {
content: content,

View File

@ -32,17 +32,24 @@ describe'/staff/edit' do
end
it 'should edit own data staff' do
request('/staff/add', {
request('/staff/invite', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
name: 'Arya Stark',
password: 'starkpassword',
email: 'arya@opensupports.com',
level: 1,
profilePic: '',
departments: '[1]'
})
recoverpassword = $database.getRow('recoverpassword', 'arya@opensupports.com', 'email')
request('/user/recover-password', {
email: 'arya@opensupports.com',
password: 'starkpassword',
token: recoverpassword['token']
})
row = $database.getRow('staff', 'arya@opensupports.com', 'email')
result = request('/staff/edit', {

View File

@ -1,21 +1,28 @@
describe'/staff/add' do
describe'/staff/invite' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
it 'should add staff member' do
result= request('/staff/add', {
result = request('/staff/invite', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
name: 'Tyrion Lannister',
email: 'tyrion@opensupports.com',
password: 'testpassword',
level: 2,
profilePic: '',
departments: '[1]'
})
(result['status']).should.equal('success')
recoverpassword = $database.getRow('recoverpassword', 'tyrion@opensupports.com', 'email')
request('/user/recover-password', {
email: 'tyrion@opensupports.com',
password: 'testpassword',
token: recoverpassword['token']
})
row = $database.getRow('staff', result['data']['id'], 'id')
(row['name']).should.equal('Tyrion Lannister')
@ -27,16 +34,15 @@ describe'/staff/add' do
(row['owners']).should.equal('4')
lastLog = $database.getLastRow('log')
(lastLog['type']).should.equal('ADD_STAFF')
(lastLog['type']).should.equal('INVITE')
end
it 'should fail if staff member is alrady a staff' do
result= request('/staff/add', {
result = request('/staff/invite', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
name: 'Tyrion Lannister',
email: 'tyrion@opensupports.com',
password: 'testpassword',
level: 2,
profilePic: '',
departments: '[1]'

View File

@ -65,6 +65,35 @@ describe'system/disable-user-system' do
(result['status']).should.equal('success')
end
it 'should be able to comment on ticket as a non-logged user' do
result = request('/ticket/create', {
title: 'Doubt about Russian language',
content: 'Stariy means old in Russian?',
departmentId: 1,
language: 'en',
name: 'Abraham Einstein',
email: 'abrahameinstein@opensupports.com'
})
(result['status']).should.equal('success')
ticketNumber = result['data']['ticketNumber']
result = request('/ticket/check', {
ticketNumber: ticketNumber,
email: 'abrahameinstein@opensupports.com',
captcha: 'valid'
})
token = result['data']['token']
(result['status']).should.equal('success');
result = request('/ticket/comment', {
content: 'I actually think it is not like that, but anyways, thanks',
ticketNumber: ticketNumber,
csrf_token: token
})
(result['status']).should.equal('success')
end
it 'should be able to assign and respond tickets' do
Scripts.login($staff[:email], $staff[:password], true);
ticket = $database.getLastRow('ticket');
@ -84,6 +113,26 @@ describe'system/disable-user-system' do
(result['status']).should.equal('success')
end
it 'should be able to get the latest events as admin' do
result = request('/staff/last-events', {
page: 1,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
(result['data'].size).should.equal(10)
end
it 'should be able to get system logs as admin' do
result = request('/system/get-logs', {
page: 1,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
(result['data'].size).should.equal(10)
end
it 'should be be able to create a ticket as an admin' do
result = request('/ticket/create', {
title: 'created by staff with user system disabled',
@ -113,7 +162,36 @@ describe'system/disable-user-system' do
(result['message']).should.equal('SYSTEM_USER_IS_ALREADY_DISABLED')
end
it 'should allow staff members to recover their passwords' do
request('/user/logout')
result = request('/user/send-recover-password', {
email: 'jorah@opensupports.com',
staff: true
})
(result['status']).should.equal('success')
token = $database.getLastRow('recoverpassword')['token'];
result = request('/user/recover-password', {
email: 'jorah@opensupports.com',
password: 's3cur3p455w0rd',
token: token
})
(result['status']).should.equal('success')
(result['data']['staff']).should.equal('1')
result = request('/user/login', {
email: 'jorah@opensupports.com',
password: 's3cur3p455w0rd',
staff: true
})
(result['status']).should.equal('success')
(result['data']['userEmail']).should.equal('jorah@opensupports.com')
end
it 'should enable the user system' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
result = request('/system/enable-user-system', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
@ -127,8 +205,7 @@ describe'system/disable-user-system' do
numberOftickets= $database.query("SELECT * FROM ticket WHERE author_email IS NULL AND author_name IS NULL AND author_id IS NOT NULL" )
(numberOftickets.num_rows).should.equal(52)
(numberOftickets.num_rows).should.equal(53)
end
it 'should not enable the user system' do
@ -140,6 +217,5 @@ describe'system/disable-user-system' do
(result['status']).should.equal('fail')
(result['message']).should.equal('SYSTEM_USER_IS_ALREADY_ENABLED')
end
end

View File

@ -182,18 +182,28 @@ describe '/ticket/comment/' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
request('/staff/add', {
result = request('/staff/invite', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
name: 'Jorah mormont',
email: 'jorah@opensupports.com',
password: 'testpassword',
level: 2,
profilePic: '',
departments: '[1]'
})
(result['status'].should.equal('success'))
request('/user/logout')
recoverpassword = $database.getRow('recoverpassword', 'jorah@opensupports.com', 'email')
request('/user/recover-password', {
email: 'jorah@opensupports.com',
password: 'testpassword',
token: recoverpassword['token']
})
Scripts.login('jorah@opensupports.com', 'testpassword', true)
result = request('/ticket/comment', {
content: 'some comment content',

View File

@ -6,17 +6,24 @@ describe '/ticket/delete' do
Scripts.createTicket('ticket_to_delete')
ticket = $database.getRow('ticket', 'ticket_to_delete', 'title')
request('/staff/add', {
request('/staff/invite', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
name: 'Ned Stark',
password: 'headless',
email: 'ned@opensupports.com',
level: 3,
profilePic: '',
departments: '[1]'
})
recoverpassword = $database.getRow('recoverpassword', 'ned@opensupports.com', 'email')
request('/user/recover-password', {
email: 'ned@opensupports.com',
password: 'headless',
token: recoverpassword['token']
})
request('/user/logout')
Scripts.login('ned@opensupports.com', 'headless', true)
@ -80,16 +87,24 @@ describe '/ticket/delete' do
ticket = $database.getRow('ticket', 'ticket_to_delete_4', 'title');
request('/staff/add', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
name: 'Joan Chris',
password: 'theyaregonnafireme',
email: 'uselessstaff@opensupports.com',
level: 2,
profilePic: '',
departments: '[1]'
request('/staff/invite', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
name: 'Joan Chris',
email: 'uselessstaff@opensupports.com',
level: 2,
profilePic: '',
departments: '[1]'
})
recoverpassword = $database.getRow('recoverpassword', 'uselessstaff@opensupports.com', 'email')
request('/user/recover-password', {
email: 'uselessstaff@opensupports.com',
password: 'theyaregonnafireme',
token: recoverpassword['token']
})
request('/user/logout')
Scripts.login('uselessstaff@opensupports.com', 'theyaregonnafireme',true)