mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-29 00:34:35 +02:00
Merge pull request #313 from ivandiazwm/master
Replace text editor, add automated image uploading, fix max-size issue
This commit is contained in:
commit
158bb099e5
@ -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
|
||||
|
@ -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/`,
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
@ -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);
|
||||
|
@ -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,
|
||||
@ -214,7 +215,7 @@ class TicketViewer extends React.Component {
|
||||
{this.renderCustomResponses()}
|
||||
<div className="ticket-viewer__response-field row">
|
||||
<Form {...this.getCommentFormProps()}>
|
||||
<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>
|
||||
@ -386,7 +387,7 @@ class TicketViewer extends React.Component {
|
||||
dataAsForm: true,
|
||||
data: _.extend({
|
||||
ticketNumber: this.props.ticket.ticketNumber
|
||||
}, formState)
|
||||
}, formState, TextEditor.getContentFormData(formState.content))
|
||||
}).then(this.onCommentSuccess.bind(this), this.onCommentFail.bind(this));
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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},
|
||||
|
@ -323,6 +323,7 @@ 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',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'Você se registrou com sucesso em nosso sistema de suporte.',
|
||||
|
@ -323,6 +323,7 @@ export default {
|
||||
'INVALID_EMAIL_OR_TICKET_NUMBER': '電子郵件或機票號無效',
|
||||
'INVALID_FILE': '無效文件',
|
||||
'ERRORS_FOUND': '發現錯誤',
|
||||
'ERROR_IMAGE_SIZE': '没有图像的大小可以超过{size}MB',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': '您已在我們的支持系統中成功註冊',
|
||||
|
@ -323,6 +323,7 @@ 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',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'Sie haben sich erfolgreich in unserem Support-System registriert.',
|
||||
|
@ -324,6 +324,7 @@ 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',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
|
||||
|
@ -348,6 +348,7 @@ 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',
|
||||
|
||||
'LAST_7_DAYS': 'Últimos 7 dias',
|
||||
'LAST_30_DAYS': 'Últimos 30 dias',
|
||||
|
@ -323,6 +323,7 @@ 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',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'Vous êtes inscrit avec succès dans notre système de support.',
|
||||
|
@ -324,6 +324,7 @@
|
||||
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Μη έγκυρη ηλεκτρονική διεύθυνση ή αριθμός εισιτηρίου',
|
||||
'INVALID_FILE': 'Μη έγκυρο αρχείο',
|
||||
'ERRORS_FOUND': 'Βρέθηκαν Σφάλματα',
|
||||
'ERROR_IMAGE_SIZE': 'Καμία εικόνα δεν μπορεί να έχει μέγεθος μεγαλύτερο από {size} MB',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'Έχετε εγγραφεί με επιτυχία στο σύστημα υποστήριξης μας.',
|
||||
|
@ -323,6 +323,7 @@ export default {
|
||||
'INVALID_EMAIL_OR_TICKET_NUMBER': 'अमान्य ईमेल या टिकट नंबर',
|
||||
'INVALID_FILE': 'अवैध फाइल',
|
||||
'ERRORS_FOUND': 'त्रुटियां मिलीं',
|
||||
'ERROR_IMAGE_SIZE': 'कोई छवि {size} एमबी से अधिक आकार नहीं हो सकती है',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'आप हमारे समर्थन प्रणाली में सफलतापूर्वक दर्ज कर लिया है।',
|
||||
|
@ -323,6 +323,7 @@ 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',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'È stato registrato con successo nel nostro sistema di supporto.',
|
||||
|
@ -323,6 +323,7 @@ export default {
|
||||
'INVALID_EMAIL_OR_TICKET_NUMBER': '電子メールまたはチケット番号が無効です',
|
||||
'INVALID_FILE': '無効なファイル',
|
||||
'ERRORS_FOUND': 'エラーが見つかりました',
|
||||
'ERROR_IMAGE_SIZE': 'イメージのサイズが{size} MBを超えることはできません',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'あなたは私たちのサポートシステムに正常に登録しました。',
|
||||
|
@ -324,6 +324,7 @@ 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',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'U hebt zich succesvol geregistreerd in ons ondersteuningssysteem.',
|
||||
|
@ -323,6 +323,7 @@ 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',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'Você se registrou com sucesso em nosso sistema de suporte.',
|
||||
|
@ -322,6 +322,7 @@ export default {
|
||||
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Неправильный номер эл. Почты или номера билета.',
|
||||
'INVALID_FILE': 'неверный файл',
|
||||
'ERRORS_FOUND': 'Ошибки найдены',
|
||||
'ERROR_IMAGE_SIZE': 'Изображение не может иметь размер больше {size} МБ',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'Вы успешно зарегистрировались в нашей системе поддержки.',
|
||||
|
@ -323,6 +323,7 @@ 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',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'Destek sistemimize başarılı bir şekilde kayıt oldunuz.',
|
||||
|
@ -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'),
|
||||
};
|
||||
}
|
||||
|
||||
|
24
client/src/lib-app/validations/image-size-validator.js
Normal file
24
client/src/lib-app/validations/image-size-validator.js
Normal 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;
|
@ -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;
|
||||
|
@ -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'),
|
||||
|
@ -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
|
||||
|
42
client/src/lib-core/base64-image-parser.js
Normal file
42
client/src/lib-core/base64-image-parser.js
Normal 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});
|
||||
},
|
||||
};
|
@ -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'
|
||||
};
|
||||
|
@ -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
7
client/src/scss/quill.snow.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -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.**
|
||||
|
@ -37,3 +37,6 @@ db:
|
||||
|
||||
sh:
|
||||
@docker exec -it opensupports-srv bash
|
||||
|
||||
doc:
|
||||
@apidoc -i models/ -i data/ -i libs/ -i controllers/ -o apidoc/
|
||||
|
@ -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.
|
||||
*/
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
]);
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* @api {post} /system/check-requirements Checks requirements
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName Check requirements
|
||||
*
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* @api {post} /system/csv-import CSV import
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName CSV import
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* @api {post} /system/disable-registration Disable registration
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName Disable registration
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* @api {post} /system/edit-settings Edit settings
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName Edit settings
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* @api {post} /system/get-settings Get settings
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName Get settings
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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'),
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
|
||||
|
||||
/**
|
||||
* @api {post} /ticket/close Close ticket
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName Close
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
@ -16,11 +16,15 @@ DataValidator::with('CustomValidations', true);
|
||||
*
|
||||
* @apiParam {String} content Content of the comment.
|
||||
* @apiParam {Number} ticketNumber The number of the ticket to comment.
|
||||
* @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
|
||||
*
|
||||
@ -105,11 +109,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()
|
||||
));
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
|
||||
|
||||
/**
|
||||
* @api {post} /ticket/edit-custom-response Edit custom response
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName Edit custom response
|
||||
*
|
||||
|
@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true);
|
||||
|
||||
/**
|
||||
* @api {post} /ticket/get-custom-responses Get custom responses
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName Get custom responses
|
||||
*
|
||||
|
@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
|
||||
DataValidator::with('CustomValidations', true);
|
||||
/**
|
||||
* @api {post} /ticket/get Get ticket
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName Get ticket
|
||||
*
|
||||
|
@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator;
|
||||
|
||||
/**
|
||||
* @api {post} /ticket/re-open Reopen ticket
|
||||
* @apiVersion 4.2.0
|
||||
* @apiVersion 4.3.0
|
||||
*
|
||||
* @apiName Reopen ticket
|
||||
*
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user