Merge branch 'master' into master

This commit is contained in:
Ivan Diaz 2018-10-01 17:22:43 -03:00 committed by GitHub
commit 303f71dab3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
147 changed files with 955 additions and 422 deletions

View File

@ -12,8 +12,8 @@ before_install:
- rvm use 2.2 --install --binary --fuzzy
- ruby --version
- mysql -e 'CREATE DATABASE development;'
- nvm install 4.4.7
- npm install -g npm@2.15.8
- nvm install 6.14.4
- npm install -g npm@6.1.0
- npm install -g mocha
- cd client
- npm install

View File

@ -56,6 +56,7 @@ Just as there is a `gulp dev` task for development, there is also a `gulp prod`
- `make db` access to mysql database console
- `make sh` access to backend docker container bash
- `make test` run phpunit tests
- `make doc` to build the documentation (requires `apidoc`)
Server api runs on `http://localhost:8080/`
Also, there's a *phpmyadmin* instance running on `http://localhost:6060/`,

View File

@ -58,16 +58,15 @@
"axios": "^0.18.0",
"chart.js": "^2.4.0",
"classnames": "^2.2.5",
"draft-js": "^0.10.5",
"draftjs-to-html": "^0.8.4",
"history": "^3.0.0",
"html-to-draftjs": "^1.4.0",
"html-to-text": "^4.0.0",
"keycode": "^2.1.4",
"localStorage": "^1.0.3",
"lodash": "^3.10.0",
"messageformat": "^0.2.2",
"qs": "^6.5.2",
"quill-image-resize-module-react": "^3.0.0",
"random-string": "^0.2.0",
"react": "^15.4.2",
"react-chartjs-2": "^2.0.0",
"react-document-title": "^1.0.2",
@ -75,6 +74,7 @@
"react-draft-wysiwyg": "^1.12.13",
"react-google-recaptcha": "^0.5.2",
"react-motion": "^0.4.7",
"react-quill": "^1.3.1",
"react-redux": "^4.4.5",
"react-router": "^3.0.2",
"react-router-redux": "^4.0.7",

View File

@ -1,17 +0,0 @@
'use strict';
var babel = require('babel-core');
module.exports = {
process: function(src, filename) {
// Ignore files other than .js, .es, .jsx or .es6
if (!babel.canCompile(filename)) {
return '';
}
// Ignore all files within node_modules
if (filename.indexOf('node_modules') === -1) {
return babel.transform(src, {filename: filename}).code;
}
return src;
}
};

View File

@ -1,4 +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';
@ -9,12 +11,14 @@ 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 TextEditor from 'core-components/text-editor';
class ArticleAddModal extends React.Component {
static propTypes = {
topicId: React.PropTypes.number.isRequired,
topicName: React.PropTypes.string.isRequired,
position: React.PropTypes.number.isRequired
position: React.PropTypes.number.isRequired,
allowAttachments: React.PropTypes.bool
};
state = {
@ -27,7 +31,7 @@ class ArticleAddModal extends React.Component {
<Header title={i18n('ADD_ARTICLE')} description={i18n('ADD_ARTICLE_DESCRIPTION', {category: this.props.topicName})} />
<Form onSubmit={this.onAddNewArticleFormSubmit.bind(this)} loading={this.state.loading}>
<FormField name="title" label={i18n('TITLE')} field="input" fieldProps={{size: 'large'}} validation="TITLE" required/>
<FormField name="content" label={i18n('CONTENT')} field="textarea" validation="TEXT_AREA" required/>
<FormField name="content" label={i18n('CONTENT')} field="textarea" validation="TEXT_AREA" required fieldProps={{allowImages: this.props.allowAttachments}}/>
<SubmitButton type="secondary">{i18n('ADD_ARTICLE')}</SubmitButton>
<Button className="article-add-modal__cancel-button" type="link" onClick={(event) => {
event.preventDefault();
@ -45,12 +49,12 @@ class ArticleAddModal extends React.Component {
API.call({
path: '/article/add',
data: {
dataAsForm: true,
data: _.extend(TextEditor.getContentFormData(form.content), {
title: form.title,
content: form.content,
topicId: this.props.topicId,
position: this.props.position
}
})
}).then(() => {
ModalContainer.closeModal();
@ -64,5 +68,8 @@ class ArticleAddModal extends React.Component {
});
}
}
export default ArticleAddModal;
export default connect((store) => {
return {
allowAttachments: store.config['allow-attachments']
};
})(ArticleAddModal);

View File

@ -29,6 +29,7 @@ class TicketViewer extends React.Component {
assignmentAllowed: React.PropTypes.bool,
staffMembers: React.PropTypes.array,
staffMembersLoaded: React.PropTypes.bool,
allowAttachments: React.PropTypes.bool,
userId: React.PropTypes.number,
userStaff: React.PropTypes.bool,
userDepartments: React.PropTypes.array,
@ -218,7 +219,7 @@ class TicketViewer extends React.Component {
{this.renderPrivate()}
</div>
<div className="ticket-viewer__response-field row">
<FormField name="content" validation="TEXT_AREA" required field="textarea" />
<FormField name="content" validation="TEXT_AREA" required field="textarea" fieldProps={{allowImages: this.props.allowAttachments}}/>
{(this.props.allowAttachments) ? <FormField name="file" field="file"/> : null}
<div className="ticket-viewer__response-buttons">
<SubmitButton type="secondary">{i18n('RESPOND_TICKET')}</SubmitButton>
@ -404,7 +405,7 @@ class TicketViewer extends React.Component {
dataAsForm: true,
data: _.extend({
ticketNumber: this.props.ticket.ticketNumber
}, formState, {private: formState.private ? 1 : 0})
}, formState, {private: formState.private ? 1 : 0}, TextEditor.getContentFormData(formState.content))
}).then(this.onCommentSuccess.bind(this), this.onCommentFail.bind(this));
}

View File

@ -22,7 +22,8 @@ class AdminPanelViewArticle extends React.Component {
static propTypes = {
topics: React.PropTypes.array,
loading: React.PropTypes.bool
loading: React.PropTypes.bool,
allowAttachments: React.PropTypes.bool
};
static defaultProps = {
@ -95,7 +96,7 @@ class AdminPanelViewArticle extends React.Component {
</Button>
</div>
<FormField name="title" label={i18n('TITLE')} />
<FormField name="content" label={i18n('CONTENT')} field="textarea" />
<FormField name="content" label={i18n('CONTENT')} field="textarea" fieldProps={{allowImages: this.props.allowAttachments}}/>
</Form>
);
}
@ -129,11 +130,11 @@ class AdminPanelViewArticle extends React.Component {
onFormSubmit(form) {
API.call({
path: '/article/edit',
data: {
dataAsForm: true,
data: _.extend(TextEditor.getContentFormData(form.content), {
articleId: this.findArticle().id,
title: form.title,
content: form.content
}
title: form.title
})
}).then(() => {
this.props.dispatch(ArticlesActions.retrieveArticles());
this.setState({
@ -162,6 +163,7 @@ class AdminPanelViewArticle extends React.Component {
export default connect((store) => {
return {
allowAttachments: store.config['allow-attachments'],
topics: store.articles.topics,
loading: store.articles.loading
};

View File

@ -5,6 +5,7 @@ import API from 'lib-app/api-call';
import DateTransformer from 'lib-core/date-transformer';
import Header from 'core-components/header';
import InfoTooltip from 'core-components/info-tooltip';
import Table from 'core-components/table';
import SearchBox from 'core-components/search-box';
import Button from 'core-components/button';
@ -96,9 +97,12 @@ class AdminPanelListUsers extends React.Component {
getUserRow(user) {
return {
name: (
<Button className="admin-panel-list-users__name-link" type="link" route={{to: '/admin/panel/users/view-user/' + user.id}}>
{user.name}
</Button>
<div>
<Button className="admin-panel-list-users__name-link" type="link" route={{to: '/admin/panel/users/view-user/' + user.id}}>
{user.name}
</Button>
{user.disabled ? this.renderDisabled() : null}
</div>
),
email: user.email,
tickets: (
@ -110,6 +114,12 @@ class AdminPanelListUsers extends React.Component {
};
}
renderDisabled() {
return (
<InfoTooltip className="admin-panel-list-users__name-disabled" type="warning" text={i18n('USER_DISABLED')} />
);
}
onSearch(query) {
this.retrieveUsers({
page: 1,
@ -154,7 +164,7 @@ class AdminPanelListUsers extends React.Component {
API.call({
path: '/user/get-users',
data: data
}).then(this.onUsersRetrieved.bind(this)).catch(this.onUsersRejected.bind(this));
}).catch(this.onUsersRejected.bind(this)).then(this.onUsersRetrieved.bind(this));
}
onCreateUser(user) {
@ -177,7 +187,7 @@ class AdminPanelListUsers extends React.Component {
pages: result.data.pages * 1,
users: result.data.users,
orderBy: result.data.orderBy,
desc: (result.data.desc === '1'),
desc: (result.data.desc*1),
error: false,
loading: false
});

View File

@ -21,18 +21,12 @@ class AdminPanelViewUser extends React.Component {
verified: true,
tickets: [],
invalid: false,
loading: true
loading: true,
disabled: false
};
componentDidMount() {
API.call({
path: '/user/get-user',
data: {
userId: this.props.params.userId
}
}).then(this.onUserRetrieved.bind(this)).catch(() => this.setState({
invalid: true
}));
this.retrieveUser();
}
render() {
@ -43,7 +37,7 @@ class AdminPanelViewUser extends React.Component {
</div>
);
}
renderInvalid() {
return (
<div className="admin-panel-view-user__invalid">
@ -51,7 +45,7 @@ class AdminPanelViewUser extends React.Component {
</div>
);
}
renderUserInfo() {
return (
<div className="admin-panel-view-user__content">
@ -60,6 +54,7 @@ class AdminPanelViewUser extends React.Component {
{i18n('NAME')}
<div className="admin-panel-view-user__info-box">
{this.state.name}
{(this.state.disabled) ? this.renderDisabled() : null}
</div>
</div>
<div className="admin-panel-view-user__info-item">
@ -69,8 +64,17 @@ class AdminPanelViewUser extends React.Component {
{(!this.state.verified) ? this.renderNotVerified() : null}
</div>
</div>
<div className="admin-panel-view-user__delete-button">
<Button onClick={this.onDeleteClick.bind(this)} size="medium">{i18n('DELETE_AND_BAN')}</Button>
<div className="admin-panel-view-user__action-buttons">
<Button
className="admin-panel-view-user__action-button"
onClick={this.onDisableClick.bind(this)}
size="medium"
type={this.state.disabled ? 'tertiary' : 'primary'}>
{i18n(this.state.disabled ? 'ENABLE_USER' : 'DISABLE_USER')}
</Button>
<Button className="admin-panel-view-user__action-button" onClick={this.onDeleteClick.bind(this)} size="medium">
{i18n('DELETE_AND_BAN')}
</Button>
</div>
</div>
<span className="separator" />
@ -88,6 +92,12 @@ class AdminPanelViewUser extends React.Component {
);
}
renderDisabled() {
return (
<InfoTooltip className="admin-panel-view-user__unverified" type="warning" text={i18n('USER_DISABLED')} />
);
}
getTicketListProps() {
return {
type: 'secondary',
@ -104,14 +114,31 @@ class AdminPanelViewUser extends React.Component {
email: result.data.email,
verified: result.data.verified,
tickets: result.data.tickets,
disabled: result.data.disabled,
loading: false
});
}
onDisableClick() {
AreYouSure.openModal(
i18n(this.state.disabled ? 'ENABLE_USER_DESCRIPTION' : 'DISABLE_USER_DESCRIPTION'),
this.disableUser.bind(this)
);
}
onDeleteClick() {
AreYouSure.openModal(i18n('DELETE_USER_DESCRIPTION'), this.deleteUser.bind(this))
}
disableUser() {
API.call({
path: this.state.disabled ? '/user/enable' : '/user/disable',
data: {
userId: this.props.params.userId
}
}).then(this.retrieveUser.bind(this));
}
deleteUser() {
API.call({
path: '/user/delete',
@ -127,6 +154,17 @@ class AdminPanelViewUser extends React.Component {
}).then(() => history.push('/admin/panel/users/list-users'));
});
}
retrieveUser() {
API.call({
path: '/user/get-user',
data: {
userId: this.props.params.userId
}
}).then(this.onUserRetrieved.bind(this)).catch(() => this.setState({
invalid: true
}));
}
}
export default connect((store) => {

View File

@ -8,7 +8,7 @@
&-item {
display: inline-block;
margin-right: 20px;
width: 200px;
min-width: 200px;
}
&-box {
@ -20,10 +20,14 @@
}
}
&__delete-button {
&__action-buttons {
margin-top: 20px;
}
&__action-button {
margin-right: 20px;
}
&__tickets-title {
font-size: $font-size--md;
margin-bottom: 20px;
@ -33,4 +37,4 @@
&__unverified {
margin-left: 15px;
}
}
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import {connect} from 'react-redux';
import history from 'lib-app/history';
import i18n from 'lib-app/i18n';
@ -57,7 +57,13 @@ class CreateTicketForm extends React.Component {
size: 'medium'
}}/>
</div>
<FormField label={i18n('CONTENT')} name="content" validation="TEXT_AREA" required field="textarea" />
<FormField
label={i18n('CONTENT')}
name="content"
validation="TEXT_AREA"
fieldProps={{allowImages: this.props.allowAttachments}}
required
field="textarea" />
{(this.props.allowAttachments) ? this.renderFileUpload() : null}
{(!this.props.userLogged) ? this.renderCaptcha() : null}
<SubmitButton>{i18n('CREATE_TICKET')}</SubmitButton>
@ -125,7 +131,7 @@ class CreateTicketForm extends React.Component {
API.call({
path: '/ticket/create',
dataAsForm: true,
data: _.extend({}, formState, {
data: _.extend({}, formState, TextEditor.getContentFormData(formState.content), {
captcha: captcha && captcha.getValue(),
departmentId: SessionStore.getDepartments()[formState.departmentIndex].id
})

View File

@ -114,6 +114,8 @@ class MainHomePageLoginWidget extends React.Component {
errors.password = i18n('ERROR_PASSWORD');
} else if (this.props.session.failMessage === 'UNVERIFIED_USER') {
errors.email = i18n('UNVERIFIED_EMAIL');
} else if (this.props.session.failMessage === 'USER_DISABLED') {
errors.email = i18n('USER_DISABLED');
}
}

View File

@ -13,6 +13,66 @@ class MainSignUpPage extends React.Component {
</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,13 +1,11 @@
// MOCKS
const ValidationFactoryMock = require('lib-app/__mocks__/validations/validation-factory-mock');
const TextEditorMock = require('core-components/__mocks__/text-editor-mock');
const FormField = ReactMock();
// COMPONENT
const Form = requireUnit('core-components/form', {
'lib-app/validations/validator-factory': ValidationFactoryMock,
'core-components/form-field': FormField,
'core-components/text-editor': TextEditorMock
});
describe('Form component', function () {
@ -187,18 +185,6 @@ describe('Form component', function () {
expect(form.props.onSubmit).to.not.have.been.called;
});
it('should transform TextEdit value to HTML', function () {
form.state.form.first = TextEditorMock.createEmpty();
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
expect(TextEditorMock.getHTMLFromEditorState).to.have.been.calledWith(form.state.form.first);
expect(form.props.onSubmit).to.have.been.calledWith({
first: 'HTML_CODE',
second: 'value2',
third: 'value3'
});
});
it('should focus the first field with error', function () {
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.performValidation = stub().returns('MOCK_ERROR_2');

View File

@ -6,7 +6,6 @@ import {reactDFS, renderChildrenWithProps} from 'lib-core/react-dfs';
import ValidationFactory from 'lib-app/validations/validator-factory';
import FormField from 'core-components/form-field';
import TextEditor from 'core-components/text-editor';
class Form extends React.Component {
@ -160,13 +159,7 @@ class Form extends React.Component {
handleSubmit(event) {
event.preventDefault();
const form = _.mapValues(this.getFormValue(), (field) => {
if (TextEditor.isEditorState(field)) {
return TextEditor.getHTMLFromEditorState(field);
} else {
return field;
}
});
const form = this.getFormValue();
if (this.hasFormErrors()) {
this.updateErrors(this.getAllFieldErrors(), this.focusFirstErrorField.bind(this));
@ -180,10 +173,7 @@ class Form extends React.Component {
form[fieldName] = event.target.value;
this.setState({
form: form
});
if(this.props.values === undefined) this.setState({form});
if (this.props.onChange) {
this.props.onChange(form);
@ -213,7 +203,7 @@ class Form extends React.Component {
}
getFormValue() {
return this.props.values || this.state.form;
return (this.props.values !== undefined) ? this.props.values : this.state.form;
}
focusFirstErrorField() {

View File

@ -1,62 +1,64 @@
import React from 'react';
import classNames from 'classnames';
import {Editor} from 'react-draft-wysiwyg';
import {EditorState, ContentState, convertToRaw} from 'draft-js';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
import ReactQuill, { Quill } from 'react-quill';
import ImageResize from 'quill-image-resize-module-react';
import {isIE} from 'lib-core/navigator';
import Base64ImageParser from 'lib-core/base64-image-parser';
Quill.register('modules/ImageResize', ImageResize);
class TextEditor extends React.Component {
static propTypes = {
errored: React.PropTypes.bool,
onChange: React.PropTypes.func,
value: React.PropTypes.oneOfType([
React.PropTypes.object, React.PropTypes.string
])
value: React.PropTypes.string,
allowImages: React.PropTypes.bool
};
static createEmpty() {
if(isIE()) return '';
return EditorState.createEmpty();
return '';
}
static getEditorStateFromHTML(htmlString) {
if(isIE()) return htmlString;
const blocksFromHTML = htmlToDraft(htmlString);
const state = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
return EditorState.createWithContent(state);
return htmlString;
}
static getHTMLFromEditorState(editorState) {
if(isIE()) return editorState;
return draftToHtml(convertToRaw(editorState.getCurrentContent()));
return editorState;
}
static isEditorState(editorState) {
if(isIE()) return typeof editorState === 'String';
return editorState && editorState.getCurrentContent;
return typeof editorState === 'String';
}
static getContentFormData(content) {
const images = Base64ImageParser.getImagesSrc(content).map(Base64ImageParser.dataURLtoFile);
const contentFormData = {
'content': Base64ImageParser.removeImagesSrc(content),
'images': images.length,
};
images.forEach((image, index) => contentFormData[`image_${index}`] = image);
return contentFormData;
}
state = {
value: TextEditor.createEmpty(),
value: '',
focused: false
};
render() {
return (
<div className={this.getClass()}>
{isIE() ? this.renderTextArea() : this.renderDraftJS()}
{isIE() ? this.renderTextArea() : this.renderQuill()}
</div>
);
}
renderDraftJS() {
return <Editor {...this.getEditorProps()} />;
renderQuill() {
return <ReactQuill {...this.getEditorProps()} />
}
renderTextArea() {
@ -67,7 +69,7 @@ class TextEditor extends React.Component {
onFocus={this.onEditorFocus.bind(this)}
onBlur={this.onBlur.bind(this)}
ref="editor"
value={this.props.value || this.state.value}
value={this.props.value}
/>
);
}
@ -87,46 +89,36 @@ class TextEditor extends React.Component {
getEditorProps() {
return {
wrapperClassName: 'text-editor__editor',
editorState: this.props.value || this.state.value,
ref: 'editor',
toolbar: this.getToolbarOptions(),
onEditorStateChange: this.onEditorChange.bind(this),
className: 'text-editor__editor',
value: (this.props.value !== undefined) ? this.props.value : this.state.value,
ref: "editor",
modules: this.getModulesOptions(),
onChange: this.onEditorChange.bind(this),
onFocus: this.onEditorFocus.bind(this),
onBlur: this.onBlur.bind(this)
onBlur: this.onBlur.bind(this),
onKeyDown: (e) => { if(e.key == "Tab") { e.preventDefault(); e.stopPropagation(); }}
};
}
getToolbarOptions() {
getModulesOptions() {
return {
options: ['inline', 'blockType', 'list', 'link', 'image', 'textAlign'],
inline: {
inDropdown: false,
options: ['bold', 'italic', 'underline', 'strikethrough', 'monospace']
},
blockType: {
inDropdown: true,
options: [ 'Normal', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Blockquote']
},
list: {
inDropdown: false,
options: ['unordered', 'ordered']
},
image: {
urlEnabled: true,
uploadEnabled: false,
alignmentEnabled: false
},
textAlign: {
inDropdown: false,
options: ['left', 'center', 'right', 'justify'],
toolbar: {
container: [
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ align: [] }],
['bold', 'italic', 'underline','strike', 'blockquote'],
[{'list': 'ordered'}, {'list': 'bullet'}],
['blockquote', 'code-block' ],
(this.props.allowImages) ? ['link', 'image'] : ['link']
],
},
ImageResize: {parchment: Quill.import('parchment')},
};
}
onEditorChange(value) {
if(isIE()) value = value.target.value;
this.setState({value});
if(this.props.value === undefined) this.setState({value});
if (this.props.onChange) {
this.props.onChange({target: {value}});
@ -151,11 +143,7 @@ class TextEditor extends React.Component {
focus() {
if (this.refs.editor) {
if(isIE()) {
this.refs.editor.focus();
} else {
this.refs.editor.focusEditor();
}
this.refs.editor.focus();
}
}
}

View File

@ -7,13 +7,8 @@
border: 1px solid $grey;
border-radius: 3px;
.DraftEditor-root {
height: 200px;
padding-left: 10px;
}
.public-DraftEditor-content {
height: 185px;
.ql-container {
min-height: 200px;
}
}

View File

@ -2,7 +2,7 @@ import React from 'react'
import {Motion, spring} from 'react-motion';
class Tooltip extends React.Component {
static propTypes = {
children: React.PropTypes.node,
content: React.PropTypes.node,
@ -105,4 +105,4 @@ class Tooltip extends React.Component {
}
}
export default Tooltip;
export default Tooltip;

View File

@ -21,7 +21,7 @@ module.exports = [
'session-prefix': 'opensupports-z6ctpq2winvfhchX2_',
'maintenance-mode': false,
'allow-attachments': true,
'max-size': 500,
'max-size': 1,
'departments': [
{id: 1, name: 'Sales Support', owners: 2},
{id: 2, name: 'Technical Issues', owners: 5},

View File

@ -184,6 +184,8 @@ export default {
'ADD_USER': 'Adicionar usuário',
'UPLOAD_FILE': 'Subir arquivo',
'PRIVATE': 'privado',
'ENABLE_USER': 'Ativar usuário',
'DISABLE_USER': 'Desativar usuário',
'CHART_CREATE_TICKET': 'Chamados criados',
'CHART_CLOSE': 'Chamados fechados',
@ -243,7 +245,7 @@ export default {
'INSTALLATION_COMPLETED': 'Instalação completa.',
'INSTALLATION_COMPLETED_DESCRIPTION': 'A instalação do OpenSupports está concluída. Redirecionando para o painel de administração ...',
'STEP_TITLE': 'Passo {current} de {total} - {title}',
'STEP_TITLE': 'Passo {current} de {total} - {title}',
'STEP_1_DESCRIPTION': 'Selecione o idioma preferido para o assistente de instalação.',
'STEP_2_DESCRIPTION': 'Aqui estão listados os requisitos para executar o OpenSupports. Certifique-se de que todos os requisitos estão satisfeitos.',
'STEP_3_DESCRIPTION': 'Preencha a configuração do banco de dados MySQL.',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Ativar / desativar o uso de um sistema de usuário. Se você desativá-lo, todos os usuários serão excluídos, mas os chamados serão mantidos. Se você ativá-lo, os chamados de usuários existentes serão criados.',
'CSV_DESCRIPTION': 'O arquivo CSV deve ter 3 colunas: e-mail, senha, nome. Não há limite na contagem de linhas. Ele será criado um usuário por linha no arquivo.',
'SMTP_SERVER_DESCRIPTION': 'A configuração do servidor SMTP permite que o aplicativo envie e-mails. Se você não configurá-lo, nenhum e-mail será enviado pela OpenSupports.',
'ENABLE_USER_DESCRIPTION': 'Essa ação permite que o usuário faça login e crie tickets.',
'DISABLE_USER_DESCRIPTION': 'O usuário será desativado e não poderá fazer login e criar tickets.',
//ERRORS
'EMAIL_OR_PASSWORD': 'E-mail ou senha inválidos',
@ -324,6 +328,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Número de e-mail ou chamado inválido',
'INVALID_FILE': 'arquivo inválido',
'ERRORS_FOUND': 'Erros encontrados',
'ERROR_IMAGE_SIZE': 'Nenhuma imagem pode ter um tamanho maior que {size} MB',
'USER_DISABLED': 'Esta conta está desativada.',
//MESSAGES
'SIGNUP_SUCCESS': 'Você se registrou com sucesso em nosso sistema de suporte.',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': '添加用户',
'UPLOAD_FILE': '上传文件',
'PRIVATE': '私人的',
'ENABLE_USER': '启用用户',
'DISABLE_USER': '禁用用户',
'CHART_CREATE_TICKET': '已創建門票',
'CHART_CLOSE': '門票已關閉',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': '啟用/禁用用戶系統的使用。如果你禁用它,所有的用戶將被刪除,但票將被保留。如果啟用,將創建現有票證的用戶。',
'CSV_DESCRIPTION': 'CSV文件必須有3列電子郵件密碼名稱。行數沒有限制。它將在文件中的每行中創建一個用戶。',
'SMTP_SERVER_DESCRIPTION': 'SMTP服务器允许应用程序发送邮件。 如果您没有配置OpenSupports将不会发送任何电子邮件。',
'ENABLE_USER_DESCRIPTION': '此操作允许用户登录并创建票证。',
'DISABLE_USER_DESCRIPTION': '用户将被禁用,无法登录并创建票证。',
//ERRORS
'EMAIL_OR_PASSWORD': '電子郵件或密碼無效',
@ -324,6 +328,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': '電子郵件或機票號無效',
'INVALID_FILE': '無效文件',
'ERRORS_FOUND': '發現錯誤',
'ERROR_IMAGE_SIZE': '没有图像的大小可以超过{size}MB',
'USER_DISABLED': '此帐户已被停用。',
//MESSAGES
'SIGNUP_SUCCESS': '您已在我們的支持系統中成功註冊',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'Benutzer hinzufügen',
'UPLOAD_FILE': 'Datei hochladen',
'PRIVATE': 'Privatgelände',
'ENABLE_USER': 'Benutzer aktivieren',
'DISABLE_USER': 'Benutzer deaktivieren',
'CHART_CREATE_TICKET': 'Tickets erstellt',
'CHART_CLOSE': 'Tickets geschlossen',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Aktivieren/deaktivieren Sie die Verwendung eines Benutzersystems. Wenn Sie es deaktivieren, werden alle Benutzer gelöscht, aber die Tickets werden behalten. Wenn Sie es aktivieren, werden die Benutzer der vorhandenen Tickets erstellt.',
'CSV_DESCRIPTION': 'Die CSV-Datei muss 3 Spalten haben: E-Mail-Adresse, Passwort, Name. Es gibt kein Limit in der Zeilenzahl. Es wird ein Benutzer pro Zeile in der Datei erstellt.',
'SMTP_SERVER_DESCRIPTION': 'Die Konfiguration des SMTP-Servers ermöglicht es der Software, Mails zu senden. Wenn Sie es nicht konfigurieren, werden keine E-Mails von OpenSupports gesendet.',
'ENABLE_USER_DESCRIPTION': 'Mit dieser Aktion kann der Benutzer sich anmelden und Tickets erstellen.',
'DISABLE_USER_DESCRIPTION': 'Der Benutzer wird deaktiviert und kann keine Tickets anmelden und erstellen.',
//ERRORS
'EMAIL_OR_PASSWORD': 'E-Mail-Adresse oder Passwort ungültig!',
@ -324,6 +328,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Ungültige E-Mail-Adresse oder Ticketnummer!',
'INVALID_FILE': 'Ungültige Datei!',
'ERRORS_FOUND': 'Fehler gefunden!',
'ERROR_IMAGE_SIZE': 'Kein Bild darf größer als {size} MB sein',
'USER_DISABLED': 'Dieser Account ist deaktiviert.',
//MESSAGES
'SIGNUP_SUCCESS': 'Sie haben sich erfolgreich in unserem Support-System registriert.',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'Add user',
'UPLOAD_FILE': 'Upload file',
'PRIVATE': 'private',
'ENABLE_USER': 'Enable User',
'DISABLE_USER': 'Disable User',
'CHART_CREATE_TICKET': 'Tickets created',
'CHART_CLOSE': 'Tickets closed',
@ -297,6 +299,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Enable/disable the use of an user system. If you disable it, all users will be deleted but the tickets will be kept. If you enable it, the users of existent tickets will be created.',
'CSV_DESCRIPTION': 'The CSV file must have 3 columns: email, password, name. There is no limit in row count. It will be created one user per row in the file.',
'SMTP_SERVER_DESCRIPTION': 'The configuration of the SMTP server allows the application to send mails. If you do not configure it, no emails will be sent by OpenSupports.',
'ENABLE_USER_DESCRIPTION': 'This action allows the user to sign in and create tickets.',
'DISABLE_USER_DESCRIPTION': 'User will be disabled and will not be able to sign in and create tickets.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid',
@ -325,6 +329,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Invalid email or ticket number',
'INVALID_FILE': 'Invalid file',
'ERRORS_FOUND': 'Errors found',
'ERROR_IMAGE_SIZE': 'No image can have a size greater than {size} MB',
'USER_DISABLED': 'This account is disabled.',
//MESSAGES
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'Añadir un usuario',
'UPLOAD_FILE': 'Subir archivo',
'PRIVATE': 'privado',
'ENABLE_USER': 'Habilitar usuario',
'DISABLE_USER': 'Deshabilitar usuario',
'CHART_CREATE_TICKET': 'Tickets creados',
'CHART_CLOSE': 'Tickets cerrados',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Habilitar/Deshabilitar el uso de un sistema de usuario. Si lo deshabilitas, todos los usuarios serán eliminados pero los tickets serán guardados. Si lo habilitas, se crearán los usuarios de los tickets existentes.',
'CSV_DESCRIPTION': 'El archivo CSV debe tener 3 columnas: correo electrónico, contraseña, nombre. No hay límite en el recuento de filas. Se creará un usuario por fila en el archivo.',
'SMTP_SERVER_DESCRIPTION': 'La configuracion de SMTP permite que la applicacion mande emails. Si no es configurado, ningún mail sera enviado OpenSupports.',
'ENABLE_USER_DESCRIPTION': 'Esta acción permite al usuario iniciar sesión y crear tickets..',
'DISABLE_USER_DESCRIPTION': 'El usuario estará deshabilitado y no podrá iniciar sesión y crear tickets.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email o contraseña inválida',
@ -349,6 +353,8 @@ export default {
'WILL_RECOVER_EMAIL_TEMPLATE': 'Esta plantilla de correo electrónico se recuperará a su valor predeterminado en este idioma.',
'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

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'Ajouter un utilisateur',
'UPLOAD_FILE': 'Téléverser un fichier',
'PRIVATE': 'privé',
'ENABLE_USER': 'Activer l\'utilisateur',
'DISABLE_USER': 'Désactiver l\'utilisateur',
'CHART_CREATE_TICKET': 'Tickets créés',
'CHART_CLOSE': 'Tickets fermés',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Activer / désactiver l\'utilisation d\'un système utilisateur. Si vous le désactivez, tous les utilisateurs seront supprimés, mais les tickets seront conservés. Si vous l\'activez, les utilisateurs des tickets existants seront créés.',
'CSV_DESCRIPTION': 'Le fichier CSV doit comporter 3 colonnes: email, mot de passe, nom. Il n\'y a pas de limite dans le nombre de lignes. Il sera créé un utilisateur par ligne dans le fichier.',
'SMTP_SERVER_DESCRIPTION': 'La configuration du serveur SMTP permet à l\'application d\'envoyer des mails. Si vous ne le configurez pas, aucun service d\'email ne sera envoyé par OpenSupports.',
'ENABLE_USER_DESCRIPTION': 'Cette action permet à l\'utilisateur de se connecter et de créer des tickets.',
'DISABLE_USER_DESCRIPTION': 'L\'utilisateur sera désactivé et ne pourra pas se connecter et créer des tickets.',
//ERRORS
'EMAIL_OR_PASSWORD': 'E-mail ou mot de passe invalide',
@ -324,6 +328,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Numéro de courriel ou de ticket invalide',
'INVALID_FILE': 'Fichier invalide',
'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é.',
//MESSAGES
'SIGNUP_SUCCESS': 'Vous êtes inscrit avec succès dans notre système de support.',

View File

@ -185,6 +185,8 @@
'ADD_USER': 'Πρόσθεσε χρήστη',
'UPLOAD_FILE': 'Ανέβασμα αρχείου',
'PRIVATE': 'ιδιωτικός',
'ENABLE_USER': 'Ενεργοποίηση χρήστη',
'DISABLE_USER': 'Απενεργοποίηση χρήστη',
'CHART_CREATE_TICKET': 'Τα εισιτήρια δημιουργήθηκαν',
'CHART_CLOSE': 'Τα εισιτήρια κλείσανε',
@ -297,6 +299,8 @@
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Ενεργοποίηση / απενεργοποίηση της χρήσης ενός συστήματος χρήστη. Εάν την απενεργοποιήσετε, όλοι οι χρήστες θα διαγραφούν αλλά τα εισιτήρια θα διατηρηθούν. Εάν την ενεργοποιήσετε, θα δημιουργηθούν οι χρήστες των υφιστάμενων εισιτηρίων.',
'CSV_DESCRIPTION': 'Το αρχείο CSV πρέπει να έχει 3 στήλες: email, κωδικό πρόσβασης, όνομα. Δεν υπάρχει όριο στην καταμέτρηση των σειρών. Θα δημιουργηθεί ένας χρήστης ανά σειρά στο αρχείο.',
'SMTP_SERVER_DESCRIPTION': 'Η διαμόρφωση του διακομιστή SMTP επιτρέπει στην εφαρμογή να στείλει μηνύματα. Αν δεν το ρυθμίσετε, δεν θα σταλούν μηνύματα ηλεκτρονικού ταχυδρομείου από το OpenSupport.',
'ENABLE_USER_DESCRIPTION': 'Αυτή η ενέργεια επιτρέπει στο χρήστη να συνδεθεί και να δημιουργήσει εισιτήρια.',
'DISABLE_USER_DESCRIPTION': 'Ο χρήστης θα απενεργοποιηθεί και δεν θα μπορέσει να συνδεθεί και να δημιουργήσει εισιτήρια.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Λάθος ηλεκτρονική διεύθυνση ή κωδικός πρόσβασης',
@ -325,6 +329,8 @@
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Μη έγκυρη ηλεκτρονική διεύθυνση ή αριθμός εισιτηρίου',
'INVALID_FILE': 'Μη έγκυρο αρχείο',
'ERRORS_FOUND': 'Βρέθηκαν Σφάλματα',
'ERROR_IMAGE_SIZE': 'Καμία εικόνα δεν μπορεί να έχει μέγεθος μεγαλύτερο από {size} MB',
'USER_DISABLED': 'Αυτός ο λογαριασμός είναι απενεργοποιημένος.',
//MESSAGES
'SIGNUP_SUCCESS': 'Έχετε εγγραφεί με επιτυχία στο σύστημα υποστήριξης μας.',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'उपयोगकर्ता जोड़ें',
'UPLOAD_FILE': 'दस्तावेज अपलोड करें',
'PRIVATE': 'निजी',
'ENABLE_USER': 'उपयोगकर्ता सक्षम करें',
'DISABLE_USER': 'उपयोगकर्ता को अक्षम करें',
'CHART_CREATE_TICKET': 'टिकट बनाया',
'CHART_CLOSE': 'टिकट बंद कर दिया',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'किसी उपयोगकर्ता सिस्टम के उपयोग को सक्षम / अक्षम करें यदि आप इसे अक्षम करते हैं, तो सभी उपयोगकर्ताओं को हटा दिया जाएगा लेकिन टिकट को रखा जाएगा। यदि आप इसे सक्षम करते हैं, तो विद्यमान टिकट के उपयोगकर्ता बनाए जाएंगे',
'CSV_DESCRIPTION': 'सीएसवी फ़ाइल में 3 कॉलम होना चाहिए: ईमेल, पासवर्ड, नाम। पंक्ति गणना में कोई सीमा नहीं है फ़ाइल में प्रति पंक्ति एक उपयोगकर्ता बनाया जाएगा।',
'SMTP_SERVER_DESCRIPTION': 'एसएमटीपी सर्वर का कॉन्फ़िगरेशन एप्लिकेशन को मेल भेजने की अनुमति देता है। यदि आप इसे कॉन्फ़िगर नहीं करते हैं, तो OpenSupports द्वारा कोई ईमेल नहीं भेजा जाएगा।',
'ENABLE_USER_DESCRIPTION': 'यह क्रिया उपयोगकर्ता को साइन इन करने और टिकट बनाने की अनुमति देती है।',
'DISABLE_USER_DESCRIPTION': 'उपयोगकर्ता अक्षम कर दिया जाएगा और साइन इन करने और टिकट बनाने में सक्षम नहीं होगा।',
//ERRORS
'EMAIL_OR_PASSWORD': 'ईमेल या पासवर्ड अमान्य',
@ -324,6 +328,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'अमान्य ईमेल या टिकट नंबर',
'INVALID_FILE': 'अवैध फाइल',
'ERRORS_FOUND': 'त्रुटियां मिलीं',
'ERROR_IMAGE_SIZE': 'कोई छवि {size} एमबी से अधिक आकार नहीं हो सकती है',
'USER_DISABLED': 'यह खाता अक्षम है।',
//MESSAGES
'SIGNUP_SUCCESS': 'आप हमारे समर्थन प्रणाली में सफलतापूर्वक दर्ज कर लिया है।',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'Aggiungi utente',
'UPLOAD_FILE': 'Caricare un file',
'PRIVATE': 'privato',
'ENABLE_USER': 'Abilita utente',
'DISABLE_USER': 'Disabilita utente',
'CHART_CREATE_TICKET': 'Tickets creato',
'CHART_CLOSE': 'Tickets chiuso',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Abilita / disabilita l\'utilizzo di un sistema utente. Se lo disattiverai, tutti gli utenti verranno eliminati ma i biglietti saranno conservati. Se lo abiliterai, verranno creati gli utenti dei biglietti esistenti.',
'CSV_DESCRIPTION': 'l file CSV deve avere 3 colonne: e-mail, password, nome. Non esiste alcun limite nel conteggio delle righe. Sarà creato un utente per riga nel file.',
'SMTP_SERVER_DESCRIPTION': 'La configurazione del server SMTP consente all\'applicazione di inviare messaggi. Se non lo si configura, non verrà inviata alcuna email da OpenSupports.',
'ENABLE_USER_DESCRIPTION': 'Questa azione consente all\'utente di accedere e creare ticket.',
'DISABLE_USER_DESCRIPTION': 'L\'utente sarà disabilitato e non sarà in grado di accedere e creare biglietti.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email o password errate',
@ -324,6 +328,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'E-mail o numero di ticket non validi',
'INVALID_FILE': 'File non valido',
'ERRORS_FOUND': 'Trovati errori',
'ERROR_IMAGE_SIZE': 'Nessuna immagine può avere una dimensione superiore a {size} MB',
'USER_DISABLED': 'Questo account è disabilitato.',
//MESSAGES
'SIGNUP_SUCCESS': 'È stato registrato con successo nel nostro sistema di supporto.',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'ユーザーを追加する',
'UPLOAD_FILE': 'ファイルをアップロードする',
'PRIVATE': 'プライベート',
'ENABLE_USER': 'ユーザーを有効にする',
'DISABLE_USER': 'ユーザーを無効にする',
'CHART_CREATE_TICKET': '作成されたチケット',
'CHART_CLOSE': 'チケットが閉じられました',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'ユーザシステムの使用を有効/無効にします。無効にすると、すべてのユーザーは削除されますが、チケットは保持されます。これを有効にすると、既存のチケットのユーザーが作成されます。',
'CSV_DESCRIPTION': 'CSVファイルには、電子メール、パスワード、名前の3つの列が必要です。行数に制限はありません。ファイル内で行ごとに1人ずつ作成されます',
'SMTP_SERVER_DESCRIPTION': 'SMTPサーバーの設定により、アプリケーションはメールを送信できます。 これを設定しないと、OpenSupportsから電子メールは送信されません。',
'ENABLE_USER_DESCRIPTION': 'これにより、ユーザーはサインインしてチケットを作成できます。',
'DISABLE_USER_DESCRIPTION': 'ユーザーは無効になり、ログインしてチケットを作成することはできません。',
//ERRORS
'EMAIL_OR_PASSWORD': '電子メールまたはパスワードが無効です',
@ -324,6 +328,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': '電子メールまたはチケット番号が無効です',
'INVALID_FILE': '無効なファイル',
'ERRORS_FOUND': 'エラーが見つかりました',
'ERROR_IMAGE_SIZE': 'イメージのサイズが{size} MBを超えることはできません',
'USER_DISABLED': 'このアカウントは無効です。',
//MESSAGES
'SIGNUP_SUCCESS': 'あなたは私たちのサポートシステムに正常に登録しました。',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'Voeg gebruiker toe',
'UPLOAD_FILE': 'Upload bestand',
'PRIVATE': 'privaat',
'ENABLE_USER': 'Schakel gebruiker in',
'DISABLE_USER': 'Gebruiker uitschakelen',
'CHART_CREATE_TICKET': 'Aangemaakte incidenten',
'CHART_CLOSE': 'Gesloten incidenten',
@ -297,6 +299,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Schakel het gebruik van een gebruikerssysteem in / uit. Als u dit uitschakelt, worden alle gebruikers verwijderd, maar worden de incidenten bewaard. Als u dit inschakelt, worden de gebruikers van bestaande incidenten aangemaakt.',
'CSV_DESCRIPTION': 'Het CSV-bestand moet drie kolommen bevatten: e-mailadres, wachtwoord, naam. Er is geen limiet in rijtelling. Er wordt één gebruiker per rij in de database gemaakt.',
'SMTP_SERVER_DESCRIPTION': 'De configuratie van de SMTP-server staat het systeem toe om e-mails te verzenden. Als u deze optie niet configureerd, worden er geen e-mails verzonden door OpenSupports.',
'ENABLE_USER_DESCRIPTION': 'Met deze actie kan de gebruiker inloggen en tickets maken.',
'DISABLE_USER_DESCRIPTION': 'De gebruiker wordt uitgeschakeld en kan zich niet aanmelden en geen tickets maken.',
//ERRORS
'EMAIL_OR_PASSWORD': 'E-mailadres of wachtwoord ongeldig',
@ -325,6 +329,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Ongeldig e-mailadres of incidentnummer',
'INVALID_FILE': 'Ongeldig bestand',
'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.',
//MESSAGES
'SIGNUP_SUCCESS': 'U hebt zich succesvol geregistreerd in ons ondersteuningssysteem.',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'Adicionar usuário',
'UPLOAD_FILE': 'Subir arquivo',
'PRIVATE': 'privado',
'ENABLE_USER': 'Ativar usuário',
'DISABLE_USER': 'Desativar usuário',
'CHART_CREATE_TICKET': 'Ingressos criados',
'CHART_CLOSE': 'Ingressos fechados',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Ativar / desativar o uso de um sistema de usuário. Se você desativá-lo, todos os usuários serão excluídos, mas os ingressos serão mantidos. Se você ativá-lo, os usuários de tickets existentes serão criados.',
'CSV_DESCRIPTION': 'O arquivo CSV deve ter 3 colunas: e-mail, senha, nome. Não há limite na contagem de linhas. Ele será criado um usuário por linha no arquivo.',
'SMTP_SERVER_DESCRIPTION': 'A configuração do servidor SMTP permite que o aplicativo envie e-mails. Se você não configurá-lo, nenhum e-mail será enviado pela OpenSupports.',
'ENABLE_USER_DESCRIPTION': 'Essa ação permite que o usuário faça login e crie tickets.',
'DISABLE_USER_DESCRIPTION': 'O usuário será desativado e não poderá fazer login e criar tickets.',
//ERRORS
'EMAIL_OR_PASSWORD': 'E-mail ou senha inválidos',
@ -324,6 +328,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Número de e-mail ou bilhete inválido',
'INVALID_FILE': 'arquivo inválido',
'ERRORS_FOUND': 'Erros encontrados',
'ERROR_IMAGE_SIZE': 'Nenhuma imagem pode ter um tamanho maior que {size} MB',
'USER_DISABLED': 'Esta conta está desativada.',
//MESSAGES
'SIGNUP_SUCCESS': 'Você se registrou com sucesso em nosso sistema de suporte.',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'Добавить пользователя',
'UPLOAD_FILE': 'Загрузить файл',
'PRIVATE': 'частный',
'ENABLE_USER': 'Включить пользователя',
'DISABLE_USER': 'Отключить пользователя',
'CHART_CREATE_TICKET': 'Билеты создано',
'CHART_CLOSE': ' Билеты закрыты',
@ -295,6 +297,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Включить / отключить использование пользовательской системы. Если вы отключите его, все пользователи будут удалены. Если вы включите его, будут созданы пользователи существующих билетов.',
'CSV_DESCRIPTION': 'Файл CSV должен иметь 3 столбца: адрес электронной почты, пароль и имя. Количество строк не ограничено. В файле будет создан один пользователь для каждой строки.',
'SMTP_SERVER_DESCRIPTION': 'Конфигурация SMTP-сервера позволяет приложению отправлять письма. Если вы не настроите его, никакие электронные письма не будут отправлены OpenSupports.',
'ENABLE_USER_DESCRIPTION': 'Это действие позволяет пользователю входить в систему и создавать билеты.',
'DISABLE_USER_DESCRIPTION': 'Пользователь будет отключен и не сможет войти и создать билеты',
//ERRORS
'EMAIL_OR_PASSWORD': 'электронной почты или пароль недействительный',
@ -323,6 +327,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Неправильный номер эл. Почты или номера билета.',
'INVALID_FILE': 'неверный файл',
'ERRORS_FOUND': 'Ошибки найдены',
'ERROR_IMAGE_SIZE': 'Изображение не может иметь размер больше {size} МБ',
'USER_DISABLED': 'Эта учетная запись отключена.',
//MESSAGES
'SIGNUP_SUCCESS': 'Вы успешно зарегистрировались в нашей системе поддержки.',

View File

@ -185,6 +185,8 @@ export default {
'ADD_USER': 'Kullanıcı Ekle',
'UPLOAD_FILE': 'Dosya yükleme',
'PRIVATE': 'gizli',
'ENABLE_USER': 'Kullanıcıyı Etkinleştir',
'DISABLE_USER': 'Kullanıcıyı Devre Dışı Bırak',
'CHART_CREATE_TICKET': 'Biletler oluşturuldu',
'CHART_CLOSE': 'Biletler kapandı',
@ -296,6 +298,8 @@ export default {
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Bir kullanıcı sisteminin kullanımını etkinleştirir / devre dışı bırakır. Devre dışı bırakırsanız, tüm kullanıcılar silinir ancak biletler muhafaza edilir. Etkinleştirirseniz, mevcut biletler kullanıcıları oluşturulur.',
'CSV_DESCRIPTION': 'CSV dosyasının 3 sütun olması gerekir: e-posta, şifre, ad. Satır sayısı sınırı yoktur. Dosya satır başına bir kullanıcı oluşturulur.',
'SMTP_SERVER_DESCRIPTION': 'SMTP sunucusunun yapılandırması, uygulamanın postalar göndermesine izin verir. Onu yapılandırmazsanız, OpenSupports tarafından hiçbir e-posta gönderilmeyecektir',
'ENABLE_USER_DESCRIPTION': 'Bu eylem, kullanıcının giriş yapmasına ve bilet oluşturmasına izin verir.',
'DISABLE_USER_DESCRIPTION': 'Kullanıcı devre dışı bırakılacak ve oturum açıp bilet oluşturamayacak.',
//ERRORS
'EMAIL_OR_PASSWORD': 'E-posta veya şifre geçersiz',
@ -324,6 +328,8 @@ export default {
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Geçersiz e-posta veya bilet numarası',
'INVALID_FILE': 'geçersiz dosya',
'ERRORS_FOUND': 'Hatalar bulundu',
'ERROR_IMAGE_SIZE': 'Hiçbir resmin boyutu {size} MB\'den büyük olabilir',
'USER_DISABLED': 'Bu hesap devre dışı.',
//MESSAGES
'SIGNUP_SUCCESS': 'Destek sistemimize başarılı bir şekilde kayıt oldunuz.',

View File

@ -61,6 +61,7 @@ class SessionStore {
this.setItem('user-system-enabled', configs['user-system-enabled']);
this.setItem('allow-attachments', configs['allow-attachments']);
this.setItem('maintenance-mode', configs['maintenance-mode']);
this.setItem('max-size', configs['max-size']);
}
getConfigs() {
@ -75,7 +76,8 @@ class SessionStore {
registration: (this.getItem('registration') * 1),
'user-system-enabled': (this.getItem('user-system-enabled') * 1),
'allow-attachments': (this.getItem('allow-attachments') * 1),
'maintenance-mode': (this.getItem('maintenance-mode') * 1)
'maintenance-mode': (this.getItem('maintenance-mode') * 1),
'max-size': this.getItem('max-size'),
};
}

View File

@ -0,0 +1,24 @@
import _ from 'lodash';
import Validator from 'lib-app/validations/validator';
import SessionStore from 'lib-app/session-store';
import Base64ImageParser from 'lib-core/base64-image-parser';
class ImageSizeValidator extends Validator {
constructor(errorKey = 'ERROR_IMAGE_SIZE', validator = null) {
super(validator);
this.maxSize = 1;
this.errorKey = errorKey;
}
validate(value = '', form = {}) {
let images = Base64ImageParser.getImagesSrc(value).map(Base64ImageParser.dataURLtoFile);
if(_.some(images, f => f.size > 1048576 * SessionStore.getItem('max-size'))) {
return this.getError(this.errorKey, {size: SessionStore.getItem('max-size')});
}
}
}
export default ImageSizeValidator;

View File

@ -1,22 +1,20 @@
import TextEditor from 'core-components/text-editor';
import Validator from 'lib-app/validations/validator';
class LengthValidator extends Validator {
constructor(length, errorKey = 'INVALID_VALUE', validator = null) {
super(validator);
this.minlength = length;
this.errorKey = errorKey;
}
validate(value = '', form = {}) {
if (TextEditor.isEditorState(value)) {
value = value.getCurrentContent().getPlainText();
}
let div = document.createElement("div");
div.innerHTML = value;
let text = div.textContent || div.innerText || "";
if (value.length < this.minlength) return this.getError(this.errorKey);
if (text.length < this.minlength) return this.getError(this.errorKey);
}
}
export default LengthValidator;
export default LengthValidator;

View File

@ -3,13 +3,14 @@ import EmailValidator from 'lib-app/validations/email-validator';
import RepeatPasswordValidator from 'lib-app/validations/repeat-password-validator';
import LengthValidator from 'lib-app/validations/length-validator';
import ListValidator from 'lib-app/validations/list-validator';
import ImageSizeValidator from 'lib-app/validations/image-size-validator';
let validators = {
'DEFAULT': new Validator(),
'NAME': new LengthValidator(2, 'ERROR_NAME'),
'TITLE': new LengthValidator(1, 'ERROR_TITLE'),
'EMAIL': new EmailValidator(),
'TEXT_AREA': new LengthValidator(10, 'ERROR_CONTENT_SHORT'),
'TEXT_AREA': new ImageSizeValidator(undefined, new LengthValidator(10, 'ERROR_CONTENT_SHORT')),
'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'),
'REPEAT_PASSWORD': new RepeatPasswordValidator(),
'URL': new LengthValidator(5, 'ERROR_URL'),

View File

@ -6,7 +6,7 @@ class Validator {
constructor(validator = null) {
this.previousValidator = validator;
}
performValidation(value, form) {
let error;
@ -27,9 +27,9 @@ class Validator {
if (value.length === 0) return this.getError('ERROR_EMPTY');
}
getError(errorKey) {
return i18n(errorKey);
getError(errorKey, params) {
return i18n(errorKey, params);
}
}
export default Validator
export default Validator

View File

@ -0,0 +1,42 @@
import randomString from 'random-string';
export default {
removeImagesSrc(str) {
let index=-1;
str = str.replace(/src="(data:image\/[^;]+;base64[^"]+)"/g, () => {
index++;
return `src="IMAGE_PATH_${index}"`;
});
return str;
},
getImagesSrc(str) {
let m,
urls = [],
rex = /src="(data:image\/[^;]+;base64[^"]+)"/g;
while ( m = rex.exec( str ) ) {
urls.push( m[1] );
}
return urls;
},
dataURLtoFile(dataurl) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
let extension=".jpg";
if(dataurl.indexOf("image/png") !== -1) extension=".png";
else if(dataurl.indexOf("image/tiff") !== -1) extension=".tiff";
else if(dataurl.indexOf("image/bmp") !== -1) extension=".bmp";
else if(dataurl.indexOf("image/gif") !== -1) extension=".gif";
return new File([u8arr], randomString() + extension, {type:mime});
},
};

View File

@ -4,6 +4,7 @@ var jsdom = require('jsdom').jsdom;
global.document = jsdom('<html><body></body></html>');
global.window = document.defaultView;
global.Node = global.window.Node;
global.navigator = {
userAgent: 'node.js'
};

View File

@ -4,6 +4,7 @@
@import 'scss/base';
@import 'scss/font_awesome/font-awesome';
@import 'scss/react-draft-wysiwyg';
@import 'scss/quill.snow.min';
@import 'core-components/*';
@import 'app-components/*';

7
client/src/scss/quill.snow.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -38,3 +38,30 @@ This request will return you the session data with an `userId` and a `token`. Yo
If you don't pass the userId and token, a `NO_PERMISSION` error will be returned.
Additionally, if there are no users (only staff members), you can check a ticket you created by providing your email and the ticketNumber to the `/ticket/check` path. This path will return you a `token` and `ticketNumber` you will use to comment, retrieve, or do any other operations to the ticket.
## File Attachments
We have two settings for file attachment:
* *allow-attachments* setting flag indicates if users can attach files.
* *max-size* setting indicates what is the file size limit in MB.
When you want to attach images to a ticket, comment or article; you can place the string `IMAGE_PATH_i` inside the parameter `content`.
`IMAGE_PATH_i` indicates that it should be replaced with the path of the image of index `i` (zero-indexed).
You may also include the `images` parameter indicating the number of images; and `image_i` parameters, which contain the image file object of index `i`.
For example
```
/article/add
title = 'article title'
content = 'this is an article <img src="IMAGE_PATH_0"/> with two images <img src="IMAGE_PATH_1"/>'
position = 1
topicId = 1
images = 2
image_0 = <File>
image_1 = <File>
```
This request will upload `image_0` and `image_1`. After that, it will replace `IMAGE_PATH_0` and `IMAGE_PATH_1` with the corresponding urls for each image. The rest of the request will operate normal.
**Please remember that `max-size` setting applies also to images.**

View File

@ -37,3 +37,6 @@ db:
sh:
@docker exec -it opensupports-srv bash
doc:
@apidoc -i models/ -i data/ -i libs/ -i controllers/ -o apidoc/

View File

@ -1,82 +0,0 @@
/**
* @api {post} /staff/get Get staff
* @apiVersion 4.0.0
*
* @apiName Get staff
*
* @apiGroup Staff
*
* @apiDescription This path retrieves information about a staff member.
*
* @apiPermission staff1
*
* @apiParam {Number} staffId The id of the staff member to be searched.
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {Object} data Information about a staff member
* @apiSuccess {String} data.name Staff id
* @apiSuccess {String} data.email Staff id
* @apiSuccess {String} data.profilePic Staff id
* @apiSuccess {Number} data.level Staff id
* @apiSuccess {Boolean} data.staff Staff id
* @apiSuccess {[Department](#api-Data_Structures-ObjectDepartment)[]} data.departments Array of departments that has assigned.
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data.tickets Array of tickets that has assigned.
*
*/
/**
* @api {get} /system/download Download file
* @apiVersion 4.0.0
*
* @apiName Download file
*
* @apiGroup System
*
* @apiDescription This path downloads a file.
*
* @apiPermission any
*
* @apiParam {String} file The filename to be downloaded.
*
*
* @apiSuccess {Object} file File content
*
*/
/**
* @api {post} /system/init-settings Init settings
* @apiVersion 4.0.0
*
* @apiName Init settings
*
* @apiGroup System
*
* @apiDescription This path sets the initial settings. It can only be used once during installation.
*
* @apiPermission any
*
* @apiParam {String} language Indicates the default language of the system.
* @apiParam {String} user-system-enabled Indicates if the user system should be enabled.
* @apiParam {String} registration Indicates if the registration should be enabled.
*
* @apiUse INVALID_LANGUAGE
* @apiUse INIT_SETTINGS_DONE
*
* @apiSuccess {Object} data Empty object
*
*/
/**
* @api {OBJECT} Staff Staff
* @apiVersion 4.0.0
* @apiGroup Data Structures
* @apiParam {String} name Name of the staff member.
* @apiParam {String} email Email of the staff member.
* @apiParam {String} profilePic profilePic url of the staff member.
* @apiParam {Number} level Level of the staff member.
* @apiParam {Object[]} departments The departments the staff member has assigned.
* @apiParam {[Ticket](#api-Data_Structures-ObjectTicket)[]} tickets The tickets the staff member has assigned.
* @apiParam {Number} lastLogin The last login of the staff member.
*/

View File

@ -1,10 +1,10 @@
{
"name": "OpenSupports API Documentation",
"version": "4.1.0",
"version": "4.3.0",
"title": "OpenSupports API Documentation",
"description": "Backend API documentation for developers.",
"header": {
"title": "API Standards",
"filename": "API_STANDARD.md"
}
}
}

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/add-topic Add topic
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Add topic
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/add Add article
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Add article
*
@ -18,11 +18,14 @@ DataValidator::with('CustomValidations', true);
* @apiParam {String} content Content of the new article.
* @apiParam {Number} position Position of the new article.
* @apiParam {Number} topicId Id of the articles's topic.
* @apiParam {Number} images The number of images in the content
* @apiParam image_i The image file of index `i` (mutiple params accepted)
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
* @apiUse INVALID_CONTENT
* @apiUse INVALID_TOPIC
* @apiUse INVALID_FILE
*
* @apiSuccess {Object} data Article info
* @apiSuccess {Number} data.articleId Article id
@ -53,10 +56,16 @@ class AddArticleController extends Controller {
}
public function handler() {
$content = Controller::request('content', true);
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_ARTICLE);
$imagePaths = $this->uploadImages(true);
$article = new Article();
$article->setProperties([
'title' => Controller::request('title'),
'content' => Controller::request('content', true),
'content' => $this->replaceWithImagePaths($imagePaths, $content),
'lastEdited' => Date::getCurrentDate(),
'position' => Controller::request('position') || 1
]);

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/delete-topic Delete topic
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Delete topic
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/delete Delete article
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Delete article
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/edit-topic Edit topic
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Edit topic
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/edit Edit article
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Edit a article
*
@ -19,9 +19,12 @@ DataValidator::with('CustomValidations', true);
* @apiParam {String} content The new content of the article. Optional.
* @apiParam {String} title The new title of the article. Optional.
* @apiParam {Number} position The new position of the article. Optional.
*
* @apiParam {Number} images The number of images in the content
* @apiParam image_i The image file of index `i` (mutiple params accepted)
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TOPIC
* @apiUse INVALID_FILE
*
* @apiSuccess {Object} data Empty object
*
@ -58,7 +61,13 @@ class EditArticleController extends Controller {
}
if(Controller::request('content')) {
$article->content = Controller::request('content', true);
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_ARTICLE);
$content = Controller::request('content', true);
$imagePaths = $this->uploadImages(true);
$article->content = $this->replaceWithImagePaths($imagePaths, $content);
}
if(Controller::request('title')) {
@ -77,4 +86,4 @@ class EditArticleController extends Controller {
Response::respondSuccess();
}
}
}

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/get-all Get all articles
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get all articles
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/add Add staff
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Add staff
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/assign-ticket Assign ticket
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Assign ticket
*

View File

@ -4,7 +4,7 @@ use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /staff/delete Delete staff
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Delete staff
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/edit Edit staff
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Edit staff
*
@ -114,6 +114,9 @@ class EditStaffController extends Controller {
}
}
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_PROFILE);
if($fileUploader = $this->uploadFile(true)) {
$this->staffInstance->profilePic = ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null;
}

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/get-all-tickets Get all tickets
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get all tickets
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/get-all Get all staffs
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get all staffs
*

View File

@ -4,7 +4,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/get-new-tickets Get new tickets
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get new tickets
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/get-tickets Get tickets
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get tickets
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/get Get staff
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get staff
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/last-events Get last events
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get last events
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/search-tickets Search tickets
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Search tickets
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/un-assign-ticket Un-assign ticket
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Un-assign ticket
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/add-api-key Add APIKey
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Add APIKey
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/add-department Add department
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Add department
*

View File

@ -3,7 +3,7 @@ use Ifsnop\Mysqldump as IMysqldump;
/**
* @api {post} /system/backup-database Backup database
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Backup database
*

View File

@ -2,7 +2,7 @@
/**
* @api {post} /system/check-requirements Checks requirements
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Check requirements
*

View File

@ -2,7 +2,7 @@
/**
* @api {post} /system/csv-import CSV import
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName CSV import
*

View File

@ -3,7 +3,7 @@ use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /system/delete-all-users Delete all users
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Delete all users
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/delete-api-key Delete APIKey
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Delete APIKey
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/delete-department Delete department
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Delete department
*

View File

@ -2,7 +2,7 @@
/**
* @api {post} /system/disable-registration Disable registration
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Disable registration
*

View File

@ -2,7 +2,7 @@
/**
* @api {post} /system/disable-user-system Disable user system
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Disable user system
*

View File

@ -4,7 +4,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {get} /system/download Download file
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Download file
*
@ -42,36 +42,32 @@ class DownloadController extends Controller {
$fileName = Controller::request('file');
$isStaffProfilePic = !Staff::getDataStore($fileName, 'profilePic')->isNull();
if(!$isStaffProfilePic) {
$session = Session::getInstance();
$loggedUser = Controller::getLoggedUser();
$fileDownloader = FileDownloader::getInstance();
$fileDownloader->setFileName($fileName);
if(!$session->sessionExists()) {
Response::respond403();
return;
}
$session = Session::getInstance();
$ticket = Ticket::getTicket($fileName, 'file');
if($ticket->isNull() || ($this->isNotAuthor($ticket, $loggedUser) && $this->isNotDepartmentOwner($ticket, $loggedUser))) {
$ticketEvent = Ticketevent::getDataStore($fileName, 'file');
if($ticketEvent->isNull()) {
Response::respond403();
return;
}
$ticket = $ticketEvent->ticket;
if($this->isNotAuthor($ticket, $loggedUser) && $this->isNotDepartmentOwner($ticket, $loggedUser)) {
Response::respond403();
return;
}
if(!$session->isStaffLogged()) {
switch($fileDownloader->getFilePermission()) {
case FileManager::PERMISSION_TICKET:
$ticketNumber = $fileDownloader->getTicketNumber();
$ticket = Ticket::getByTicketNumber($ticketNumber);
if($this->isNotAuthor($ticket, Controller::getLoggedUser())) {
return Response::respond403();
}
break;
case FileManager::PERMISSION_ARTICLE:
if(Controller::isUserSystemEnabled() && !$session->sessionExists()) {
return Response::respond403();
}
break;
case FileManager::PERMISSION_PROFILE:
break;
default:
return Response::respond403();
}
}
$fileDownloader = FileDownloader::getInstance();
$fileDownloader->setFileName($fileName);
$fileDownloader->download();
exit();
}
@ -82,7 +78,7 @@ class DownloadController extends Controller {
if($session->getTicketNumber()) {
return $session->getTicketNumber() !== $ticket->ticketNumber;
} else {
return $loggedUser->level >= 1 || $ticket->author->id !== $loggedUser->id;
return $ticket->author->id !== $loggedUser->id || ($loggedUser instanceof Staff) !== $ticket->authorToArray()['staff'];
}
}

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/edit-department Edit department
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Edit department
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/edit-mail-template Edit mail template
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Edit mail template
*

View File

@ -2,7 +2,7 @@
/**
* @api {post} /system/edit-settings Edit settings
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Edit settings
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/enable-registration Enable registration
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Enable registration
*

View File

@ -2,7 +2,7 @@
/**
* @api {post} /system/enable-user-system Enable user system
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Enable user system
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-api-keys Get APIKeys
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get APIKeys
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-logs Get logs
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get logs
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-mail-templates Get mail templates
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get mail templates
*

View File

@ -2,7 +2,7 @@
/**
* @api {post} /system/get-settings Get settings
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get settings
*

View File

@ -4,7 +4,7 @@ use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /system/get-stats Get stats
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Get stats
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/init-admin Init admin
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Init admin
*

View File

@ -4,7 +4,7 @@ use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /system/init-database Init database
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Init database
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/init-settings Init settings
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Init settings
*
@ -77,7 +77,7 @@ class InitSettingsController extends Controller {
'maintenance-mode' => 0,
'layout' => 'boxed',
'allow-attachments' => !!Controller::request('allow-attachments'),
'max-size' => 1024,
'max-size' => 1,
'title' => Controller::request('title') ? Controller::request('title') : 'Support Center',
'url' => Controller::request('url') ? Controller::request('url') : ('http://' . $_SERVER['HTTP_HOST']),
'registration' => !!Controller::request('registration'),

View File

@ -3,7 +3,7 @@ use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /system/installation-done Installation done
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Installation done
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/recover-mail-template Recover mail template
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Recover mail template
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/test-smtp Test SMTP Connection
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Test SMTP Connection
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/add-custom-response Add custom responses
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Add a custom response
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/change-department Change department
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Change department
*

View File

@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /ticket/change-priority Change priority
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Change priority
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/check Check ticket
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Check ticket
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/close Close ticket
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Close
*

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/comment Comment ticket
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Comment ticket
*
@ -17,11 +17,15 @@ DataValidator::with('CustomValidations', true);
* @apiParam {String} content Content of the comment.
* @apiParam {Number} ticketNumber The number of the ticket to comment.
* @apiParam {Boolean} private Indicates if the comment is not shown to users.
* @apiParam {Number} images The number of images in the content
* @apiParam image_i The image file of index `i` (mutiple params accepted)
* @apiParam file The file you with to upload.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_CONTENT
* @apiUse INVALID_TICKET
* @apiUse INVALID_TOKEN
* @apiUse INVALID_FILE
*
* @apiSuccess {Object} data Empty object
*
@ -106,11 +110,14 @@ class CommentController extends Controller {
}
private function storeComment() {
$fileUploader = $this->uploadFile();
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_TICKET, $this->ticket->ticketNumber);
$imagePaths = $this->uploadImages(Controller::isStaffLogged());
$fileUploader = $this->uploadFile(Controller::isStaffLogged());
$comment = Ticketevent::getEvent(Ticketevent::COMMENT);
$comment->setProperties(array(
'content' => $this->content,
'content' => $this->replaceWithImagePaths($imagePaths, $this->content),
'file' => ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null,
'date' => Date::getCurrentDate(),
'private' => (Controller::isStaffLogged() && Controller::request('private')) ? 1 : 0

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/create Create ticket
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Create ticket
*
@ -19,7 +19,9 @@ DataValidator::with('CustomValidations', true);
* @apiParam {Number} departmentId The id of the department of the current ticket.
* @apiParam {String} language The language of the ticket.
* @apiParam {String} email The email of the user who created the ticket.
* @apiParam {String} name The Name of the author of the ticket.
* @apiParam {Number} images The number of images in the content
* @apiParam image_i The image file of index `i` (mutiple params accepted)
* @apiParam file The file you with to upload.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TITLE
@ -28,6 +30,7 @@ DataValidator::with('CustomValidations', true);
* @apiUse INVALID_LANGUAGE
* @apiUse INVALID_CAPTCHA
* @apiUse INVALID_EMAIL
* @apiUse INVALID_FILE
*
* @apiSuccess {Object} data Information of the new ticket
* @apiSuccess {Number} data.ticketNumber Number of the new ticket
@ -69,7 +72,7 @@ class CreateController extends Controller {
]
];
if(!Controller::isUserSystemEnabled()) {
if(!Controller::isUserSystemEnabled() && !Controller::isStaffLogged()) {
$validations['permission'] = 'any';
$validations['requestData']['captcha'] = [
'validation' => DataValidator::captcha(),
@ -118,13 +121,17 @@ class CreateController extends Controller {
private function storeTicket() {
$department = Department::getDataStore($this->departmentId);
$author = Controller::getLoggedUser();
$fileUploader = $this->uploadFile();
$ticket = new Ticket();
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_TICKET, $ticket->generateUniqueTicketNumber());
$imagePaths = $this->uploadImages(Controller::isStaffLogged());
$fileUploader = $this->uploadFile(Controller::isStaffLogged());
$ticket->setProperties(array(
'title' => $this->title,
'content' => $this->content,
'content' => $this->replaceWithImagePaths($imagePaths, $this->content),
'language' => $this->language,
'department' => $department,
'file' => ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null,

View File

@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/delete-custom-response Delete custom response
* @apiVersion 4.2.0
* @apiVersion 4.3.0
*
* @apiName Delete custom response
*

Some files were not shown because too many files have changed in this diff Show More