diff --git a/.travis.yml b/.travis.yml index 7ed7d71f..2cd3045f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index 50f8b973..d73497dd 100644 --- a/README.md +++ b/README.md @@ -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/`, diff --git a/client/package.json b/client/package.json index 1aa67b0f..a0f03980 100644 --- a/client/package.json +++ b/client/package.json @@ -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", diff --git a/client/preprocessor.js b/client/preprocessor.js deleted file mode 100644 index 4a742dcf..00000000 --- a/client/preprocessor.js +++ /dev/null @@ -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; - } -}; \ No newline at end of file diff --git a/client/src/app-components/article-add-modal.js b/client/src/app-components/article-add-modal.js index 0a5e4346..8912cb52 100644 --- a/client/src/app-components/article-add-modal.js +++ b/client/src/app-components/article-add-modal.js @@ -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 {
- + {i18n('ADD_ARTICLE')} - + ); } @@ -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 }; diff --git a/client/src/app/admin/panel/users/admin-panel-list-users.js b/client/src/app/admin/panel/users/admin-panel-list-users.js index 04380bf6..b2997af2 100644 --- a/client/src/app/admin/panel/users/admin-panel-list-users.js +++ b/client/src/app/admin/panel/users/admin-panel-list-users.js @@ -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: ( - +
+ + {user.disabled ? this.renderDisabled() : null} +
), email: user.email, tickets: ( @@ -110,6 +114,12 @@ class AdminPanelListUsers extends React.Component { }; } + renderDisabled() { + return ( + + ); + } + 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 }); diff --git a/client/src/app/admin/panel/users/admin-panel-view-user.js b/client/src/app/admin/panel/users/admin-panel-view-user.js index dbf94d84..b713923b 100644 --- a/client/src/app/admin/panel/users/admin-panel-view-user.js +++ b/client/src/app/admin/panel/users/admin-panel-view-user.js @@ -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 { ); } - + renderInvalid() { return (
@@ -51,7 +45,7 @@ class AdminPanelViewUser extends React.Component {
); } - + renderUserInfo() { return (
@@ -60,6 +54,7 @@ class AdminPanelViewUser extends React.Component { {i18n('NAME')}
{this.state.name} + {(this.state.disabled) ? this.renderDisabled() : null}
@@ -69,8 +64,17 @@ class AdminPanelViewUser extends React.Component { {(!this.state.verified) ? this.renderNotVerified() : null}
-
- +
+ +
@@ -88,6 +92,12 @@ class AdminPanelViewUser extends React.Component { ); } + renderDisabled() { + return ( + + ); + } + 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) => { diff --git a/client/src/app/admin/panel/users/admin-panel-view-user.scss b/client/src/app/admin/panel/users/admin-panel-view-user.scss index 8d289ba2..b7cd144b 100644 --- a/client/src/app/admin/panel/users/admin-panel-view-user.scss +++ b/client/src/app/admin/panel/users/admin-panel-view-user.scss @@ -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; } -} \ No newline at end of file +} diff --git a/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js b/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js index d4859a08..4247f9a7 100644 --- a/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js +++ b/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js @@ -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' }}/> - + {(this.props.allowAttachments) ? this.renderFileUpload() : null} {(!this.props.userLogged) ? this.renderCaptcha() : null} {i18n('CREATE_TICKET')} @@ -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 }) diff --git a/client/src/app/main/main-home/main-home-page-login-widget.js b/client/src/app/main/main-home/main-home-page-login-widget.js index 6cb2148d..3e5fd685 100644 --- a/client/src/app/main/main-home/main-home-page-login-widget.js +++ b/client/src/app/main/main-home/main-home-page-login-widget.js @@ -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'); } } diff --git a/client/src/app/main/main-signup/main-signup-page.js b/client/src/app/main/main-signup/main-signup-page.js index 2af28912..b0634ea8 100644 --- a/client/src/app/main/main-signup/main-signup-page.js +++ b/client/src/app/main/main-signup/main-signup-page.js @@ -13,6 +13,66 @@ class MainSignUpPage extends React.Component { ); } + + renderMessage() { + switch (this.state.message) { + case 'success': + return {i18n('SIGNUP_SUCCESS')}; + case 'fail': + return {i18n('EMAIL_EXISTS')}; + 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; diff --git a/client/src/core-components/__tests__/form-test.js b/client/src/core-components/__tests__/form-test.js index 016ed26e..94530f15 100644 --- a/client/src/core-components/__tests__/form-test.js +++ b/client/src/core-components/__tests__/form-test.js @@ -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'); diff --git a/client/src/core-components/form.js b/client/src/core-components/form.js index cd1a6642..ea422d54 100644 --- a/client/src/core-components/form.js +++ b/client/src/core-components/form.js @@ -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() { diff --git a/client/src/core-components/text-editor.js b/client/src/core-components/text-editor.js index 3223a37b..ebdc2b2f 100644 --- a/client/src/core-components/text-editor.js +++ b/client/src/core-components/text-editor.js @@ -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 (
- {isIE() ? this.renderTextArea() : this.renderDraftJS()} + {isIE() ? this.renderTextArea() : this.renderQuill()}
); } - renderDraftJS() { - return ; + renderQuill() { + return } 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(); } } } diff --git a/client/src/core-components/text-editor.scss b/client/src/core-components/text-editor.scss index 07e81be1..4c516f8c 100644 --- a/client/src/core-components/text-editor.scss +++ b/client/src/core-components/text-editor.scss @@ -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; } } diff --git a/client/src/core-components/tooltip.js b/client/src/core-components/tooltip.js index 738b1051..c704b192 100644 --- a/client/src/core-components/tooltip.js +++ b/client/src/core-components/tooltip.js @@ -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; \ No newline at end of file +export default Tooltip; diff --git a/client/src/data/fixtures/system-fixtures.js b/client/src/data/fixtures/system-fixtures.js index 50e1d5d8..a60dfba5 100644 --- a/client/src/data/fixtures/system-fixtures.js +++ b/client/src/data/fixtures/system-fixtures.js @@ -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}, diff --git a/client/src/data/languages/br.js b/client/src/data/languages/br.js index 99b4a26c..62dede7f 100644 --- a/client/src/data/languages/br.js +++ b/client/src/data/languages/br.js @@ -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.', diff --git a/client/src/data/languages/cn.js b/client/src/data/languages/cn.js index 7b07922b..8c47b34e 100644 --- a/client/src/data/languages/cn.js +++ b/client/src/data/languages/cn.js @@ -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': '您已在我們的支持系統中成功註冊', diff --git a/client/src/data/languages/de.js b/client/src/data/languages/de.js index bb674974..8070bd49 100644 --- a/client/src/data/languages/de.js +++ b/client/src/data/languages/de.js @@ -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.', diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index de65e008..38c3a9cb 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -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.', diff --git a/client/src/data/languages/es.js b/client/src/data/languages/es.js index 320f7351..a375ac30 100644 --- a/client/src/data/languages/es.js +++ b/client/src/data/languages/es.js @@ -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', diff --git a/client/src/data/languages/fr.js b/client/src/data/languages/fr.js index 8aabc598..38f2e088 100644 --- a/client/src/data/languages/fr.js +++ b/client/src/data/languages/fr.js @@ -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.', diff --git a/client/src/data/languages/gr.js b/client/src/data/languages/gr.js index 8e55aa2e..4d879ce2 100644 --- a/client/src/data/languages/gr.js +++ b/client/src/data/languages/gr.js @@ -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': 'Έχετε εγγραφεί με επιτυχία στο σύστημα υποστήριξης μας.', diff --git a/client/src/data/languages/in.js b/client/src/data/languages/in.js index 301ed531..bba2f8e1 100644 --- a/client/src/data/languages/in.js +++ b/client/src/data/languages/in.js @@ -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': 'आप हमारे समर्थन प्रणाली में सफलतापूर्वक दर्ज कर लिया है।', diff --git a/client/src/data/languages/it.js b/client/src/data/languages/it.js index bb203e0c..16206d06 100644 --- a/client/src/data/languages/it.js +++ b/client/src/data/languages/it.js @@ -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.', diff --git a/client/src/data/languages/jp.js b/client/src/data/languages/jp.js index beecedae..9f086bc2 100644 --- a/client/src/data/languages/jp.js +++ b/client/src/data/languages/jp.js @@ -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': 'あなたは私たちのサポートシステムに正常に登録しました。', diff --git a/client/src/data/languages/nl.js b/client/src/data/languages/nl.js index 8a95072f..9617485d 100644 --- a/client/src/data/languages/nl.js +++ b/client/src/data/languages/nl.js @@ -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.', diff --git a/client/src/data/languages/pt.js b/client/src/data/languages/pt.js index 7536a271..e4b83918 100644 --- a/client/src/data/languages/pt.js +++ b/client/src/data/languages/pt.js @@ -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.', diff --git a/client/src/data/languages/ru.js b/client/src/data/languages/ru.js index d24745ad..54e685d3 100644 --- a/client/src/data/languages/ru.js +++ b/client/src/data/languages/ru.js @@ -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': 'Вы успешно зарегистрировались в нашей системе поддержки.', diff --git a/client/src/data/languages/tr.js b/client/src/data/languages/tr.js index 14ff6b55..7267a1c0 100644 --- a/client/src/data/languages/tr.js +++ b/client/src/data/languages/tr.js @@ -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.', diff --git a/client/src/lib-app/session-store.js b/client/src/lib-app/session-store.js index bfcf14cb..540c994b 100644 --- a/client/src/lib-app/session-store.js +++ b/client/src/lib-app/session-store.js @@ -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'), }; } diff --git a/client/src/lib-app/validations/image-size-validator.js b/client/src/lib-app/validations/image-size-validator.js new file mode 100644 index 00000000..cd8b44d3 --- /dev/null +++ b/client/src/lib-app/validations/image-size-validator.js @@ -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; diff --git a/client/src/lib-app/validations/length-validator.js b/client/src/lib-app/validations/length-validator.js index dcc03bec..3b120e07 100644 --- a/client/src/lib-app/validations/length-validator.js +++ b/client/src/lib-app/validations/length-validator.js @@ -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; \ No newline at end of file +export default LengthValidator; diff --git a/client/src/lib-app/validations/validator-factory.js b/client/src/lib-app/validations/validator-factory.js index fdd63284..c84278f6 100644 --- a/client/src/lib-app/validations/validator-factory.js +++ b/client/src/lib-app/validations/validator-factory.js @@ -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'), diff --git a/client/src/lib-app/validations/validator.js b/client/src/lib-app/validations/validator.js index 6b98ced3..7ed2af44 100644 --- a/client/src/lib-app/validations/validator.js +++ b/client/src/lib-app/validations/validator.js @@ -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 \ No newline at end of file +export default Validator diff --git a/client/src/lib-core/base64-image-parser.js b/client/src/lib-core/base64-image-parser.js new file mode 100644 index 00000000..1dd5f7ce --- /dev/null +++ b/client/src/lib-core/base64-image-parser.js @@ -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}); + }, +}; diff --git a/client/src/lib-test/preprocessor.js b/client/src/lib-test/preprocessor.js index 1904b33c..fb3b5bcd 100644 --- a/client/src/lib-test/preprocessor.js +++ b/client/src/lib-test/preprocessor.js @@ -4,6 +4,7 @@ var jsdom = require('jsdom').jsdom; global.document = jsdom(''); global.window = document.defaultView; +global.Node = global.window.Node; global.navigator = { userAgent: 'node.js' }; diff --git a/client/src/main.scss b/client/src/main.scss index e53982b3..3297c2c4 100644 --- a/client/src/main.scss +++ b/client/src/main.scss @@ -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/*'; diff --git a/client/src/scss/quill.snow.min.css b/client/src/scss/quill.snow.min.css new file mode 100644 index 00000000..cad49ac3 --- /dev/null +++ b/client/src/scss/quill.snow.min.css @@ -0,0 +1,7 @@ +/*! + * Quill Editor v1.3.6 + * https://quilljs.com/ + * Copyright (c) 2014, Jason Chen + * Copyright (c) 2013, salesforce.com + */.ql-container{box-sizing:border-box;font-family:Helvetica,Arial,sans-serif;font-size:13px;height:100%;margin:0;position:relative}.ql-container.ql-disabled .ql-tooltip{visibility:hidden}.ql-container.ql-disabled .ql-editor ul[data-checked]>li::before{pointer-events:none}.ql-clipboard{left:-100000px;height:1px;overflow-y:hidden;position:absolute;top:50%}.ql-clipboard p{margin:0;padding:0}.ql-editor{box-sizing:border-box;line-height:1.42;height:100%;outline:0;overflow-y:auto;padding:12px 15px;tab-size:4;-moz-tab-size:4;text-align:left;white-space:pre-wrap;word-wrap:break-word}.ql-editor>*{cursor:text}.ql-editor blockquote,.ql-editor h1,.ql-editor h2,.ql-editor h3,.ql-editor h4,.ql-editor h5,.ql-editor h6,.ql-editor ol,.ql-editor p,.ql-editor pre,.ql-editor ul{margin:0;padding:0;counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol,.ql-editor ul{padding-left:1.5em}.ql-editor ol>li,.ql-editor ul>li{list-style-type:none}.ql-editor ul>li::before{content:'\2022'}.ql-editor ul[data-checked=false],.ql-editor ul[data-checked=true]{pointer-events:none}.ql-editor ul[data-checked=false]>li *,.ql-editor ul[data-checked=true]>li *{pointer-events:all}.ql-editor ul[data-checked=false]>li::before,.ql-editor ul[data-checked=true]>li::before{color:#777;cursor:pointer;pointer-events:all}.ql-editor ul[data-checked=true]>li::before{content:'\2611'}.ql-editor ul[data-checked=false]>li::before{content:'\2610'}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}.ql-editor ol li:not(.ql-direction-rtl),.ql-editor ul li:not(.ql-direction-rtl){padding-left:1.5em}.ql-editor ol li.ql-direction-rtl,.ql-editor ul li.ql-direction-rtl{padding-right:1.5em}.ql-editor ol li{counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;counter-increment:list-0}.ql-editor ol li:before{content:counter(list-0,decimal) '. '}.ql-editor ol li.ql-indent-1{counter-increment:list-1}.ql-editor ol li.ql-indent-1:before{content:counter(list-1,lower-alpha) '. '}.ql-editor ol li.ql-indent-1{counter-reset:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-2{counter-increment:list-2}.ql-editor ol li.ql-indent-2:before{content:counter(list-2,lower-roman) '. '}.ql-editor ol li.ql-indent-2{counter-reset:list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-3{counter-increment:list-3}.ql-editor ol li.ql-indent-3:before{content:counter(list-3,decimal) '. '}.ql-editor ol li.ql-indent-3{counter-reset:list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-4{counter-increment:list-4}.ql-editor ol li.ql-indent-4:before{content:counter(list-4,lower-alpha) '. '}.ql-editor ol li.ql-indent-4{counter-reset:list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-5{counter-increment:list-5}.ql-editor ol li.ql-indent-5:before{content:counter(list-5,lower-roman) '. '}.ql-editor ol li.ql-indent-5{counter-reset:list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-6{counter-increment:list-6}.ql-editor ol li.ql-indent-6:before{content:counter(list-6,decimal) '. '}.ql-editor ol li.ql-indent-6{counter-reset:list-7 list-8 list-9}.ql-editor ol li.ql-indent-7{counter-increment:list-7}.ql-editor ol li.ql-indent-7:before{content:counter(list-7,lower-alpha) '. '}.ql-editor ol li.ql-indent-7{counter-reset:list-8 list-9}.ql-editor ol li.ql-indent-8{counter-increment:list-8}.ql-editor ol li.ql-indent-8:before{content:counter(list-8,lower-roman) '. '}.ql-editor ol li.ql-indent-8{counter-reset:list-9}.ql-editor ol li.ql-indent-9{counter-increment:list-9}.ql-editor ol li.ql-indent-9:before{content:counter(list-9,decimal) '. '}.ql-editor .ql-indent-1:not(.ql-direction-rtl){padding-left:3em}.ql-editor li.ql-indent-1:not(.ql-direction-rtl){padding-left:4.5em}.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right{padding-right:3em}.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right{padding-right:4.5em}.ql-editor .ql-indent-2:not(.ql-direction-rtl){padding-left:6em}.ql-editor li.ql-indent-2:not(.ql-direction-rtl){padding-left:7.5em}.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right{padding-right:6em}.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right{padding-right:7.5em}.ql-editor .ql-indent-3:not(.ql-direction-rtl){padding-left:9em}.ql-editor li.ql-indent-3:not(.ql-direction-rtl){padding-left:10.5em}.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right{padding-right:9em}.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right{padding-right:10.5em}.ql-editor .ql-indent-4:not(.ql-direction-rtl){padding-left:12em}.ql-editor li.ql-indent-4:not(.ql-direction-rtl){padding-left:13.5em}.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right{padding-right:12em}.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right{padding-right:13.5em}.ql-editor .ql-indent-5:not(.ql-direction-rtl){padding-left:15em}.ql-editor li.ql-indent-5:not(.ql-direction-rtl){padding-left:16.5em}.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right{padding-right:15em}.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right{padding-right:16.5em}.ql-editor .ql-indent-6:not(.ql-direction-rtl){padding-left:18em}.ql-editor li.ql-indent-6:not(.ql-direction-rtl){padding-left:19.5em}.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right{padding-right:18em}.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right{padding-right:19.5em}.ql-editor .ql-indent-7:not(.ql-direction-rtl){padding-left:21em}.ql-editor li.ql-indent-7:not(.ql-direction-rtl){padding-left:22.5em}.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right{padding-right:21em}.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right{padding-right:22.5em}.ql-editor .ql-indent-8:not(.ql-direction-rtl){padding-left:24em}.ql-editor li.ql-indent-8:not(.ql-direction-rtl){padding-left:25.5em}.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right{padding-right:24em}.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right{padding-right:25.5em}.ql-editor .ql-indent-9:not(.ql-direction-rtl){padding-left:27em}.ql-editor li.ql-indent-9:not(.ql-direction-rtl){padding-left:28.5em}.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right{padding-right:27em}.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right{padding-right:28.5em}.ql-editor .ql-video{display:block;max-width:100%}.ql-editor .ql-video.ql-align-center{margin:0 auto}.ql-editor .ql-video.ql-align-right{margin:0 0 0 auto}.ql-editor .ql-bg-black{background-color:#000}.ql-editor .ql-bg-red{background-color:#e60000}.ql-editor .ql-bg-orange{background-color:#f90}.ql-editor .ql-bg-yellow{background-color:#ff0}.ql-editor .ql-bg-green{background-color:#008a00}.ql-editor .ql-bg-blue{background-color:#06c}.ql-editor .ql-bg-purple{background-color:#93f}.ql-editor .ql-color-white{color:#fff}.ql-editor .ql-color-red{color:#e60000}.ql-editor .ql-color-orange{color:#f90}.ql-editor .ql-color-yellow{color:#ff0}.ql-editor .ql-color-green{color:#008a00}.ql-editor .ql-color-blue{color:#06c}.ql-editor .ql-color-purple{color:#93f}.ql-editor .ql-font-serif{font-family:Georgia,Times New Roman,serif}.ql-editor .ql-font-monospace{font-family:Monaco,Courier New,monospace}.ql-editor .ql-size-small{font-size:.75em}.ql-editor .ql-size-large{font-size:1.5em}.ql-editor .ql-size-huge{font-size:2.5em}.ql-editor .ql-direction-rtl{direction:rtl;text-align:inherit}.ql-editor .ql-align-center{text-align:center}.ql-editor .ql-align-justify{text-align:justify}.ql-editor .ql-align-right{text-align:right}.ql-editor.ql-blank::before{color:rgba(0,0,0,.6);content:attr(data-placeholder);font-style:italic;left:15px;pointer-events:none;position:absolute;right:15px}.ql-snow .ql-toolbar:after,.ql-snow.ql-toolbar:after{clear:both;content:'';display:table}.ql-snow .ql-toolbar button,.ql-snow.ql-toolbar button{background:0 0;border:none;cursor:pointer;display:inline-block;float:left;height:24px;padding:3px 5px;width:28px}.ql-snow .ql-toolbar button svg,.ql-snow.ql-toolbar button svg{float:left;height:100%}.ql-snow .ql-toolbar button:active:hover,.ql-snow.ql-toolbar button:active:hover{outline:0}.ql-snow .ql-toolbar input.ql-image[type=file],.ql-snow.ql-toolbar input.ql-image[type=file]{display:none}.ql-snow .ql-toolbar .ql-picker-item.ql-selected,.ql-snow .ql-toolbar .ql-picker-item:hover,.ql-snow .ql-toolbar .ql-picker-label.ql-active,.ql-snow .ql-toolbar .ql-picker-label:hover,.ql-snow .ql-toolbar button.ql-active,.ql-snow .ql-toolbar button:focus,.ql-snow .ql-toolbar button:hover,.ql-snow.ql-toolbar .ql-picker-item.ql-selected,.ql-snow.ql-toolbar .ql-picker-item:hover,.ql-snow.ql-toolbar .ql-picker-label.ql-active,.ql-snow.ql-toolbar .ql-picker-label:hover,.ql-snow.ql-toolbar button.ql-active,.ql-snow.ql-toolbar button:focus,.ql-snow.ql-toolbar button:hover{color:#06c}.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar button.ql-active .ql-fill,.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:focus .ql-fill,.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:hover .ql-fill,.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar button.ql-active .ql-fill,.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,.ql-snow.ql-toolbar button:focus .ql-fill,.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,.ql-snow.ql-toolbar button:hover .ql-fill,.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill{fill:#06c}.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,.ql-snow .ql-toolbar button.ql-active .ql-stroke,.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,.ql-snow .ql-toolbar button:focus .ql-stroke,.ql-snow .ql-toolbar button:focus .ql-stroke-miter,.ql-snow .ql-toolbar button:hover .ql-stroke,.ql-snow .ql-toolbar button:hover .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,.ql-snow.ql-toolbar button.ql-active .ql-stroke,.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,.ql-snow.ql-toolbar button:focus .ql-stroke,.ql-snow.ql-toolbar button:focus .ql-stroke-miter,.ql-snow.ql-toolbar button:hover .ql-stroke,.ql-snow.ql-toolbar button:hover .ql-stroke-miter{stroke:#06c}@media (pointer:coarse){.ql-snow .ql-toolbar button:hover:not(.ql-active),.ql-snow.ql-toolbar button:hover:not(.ql-active){color:#444}.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill{fill:#444}.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter{stroke:#444}}.ql-snow{box-sizing:border-box}.ql-snow *{box-sizing:border-box}.ql-snow .ql-hidden{display:none}.ql-snow .ql-out-bottom,.ql-snow .ql-out-top{visibility:hidden}.ql-snow .ql-tooltip{position:absolute;transform:translateY(10px)}.ql-snow .ql-tooltip a{cursor:pointer;text-decoration:none}.ql-snow .ql-tooltip.ql-flip{transform:translateY(-10px)}.ql-snow .ql-formats{display:inline-block;vertical-align:middle}.ql-snow .ql-formats:after{clear:both;content:'';display:table}.ql-snow .ql-stroke{fill:none;stroke:#444;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}.ql-snow .ql-stroke-miter{fill:none;stroke:#444;stroke-miterlimit:10;stroke-width:2}.ql-snow .ql-fill,.ql-snow .ql-stroke.ql-fill{fill:#444}.ql-snow .ql-empty{fill:none}.ql-snow .ql-even{fill-rule:evenodd}.ql-snow .ql-stroke.ql-thin,.ql-snow .ql-thin{stroke-width:1}.ql-snow .ql-transparent{opacity:.4}.ql-snow .ql-direction svg:last-child{display:none}.ql-snow .ql-direction.ql-active svg:last-child{display:inline}.ql-snow .ql-direction.ql-active svg:first-child{display:none}.ql-snow .ql-editor h1{font-size:2em}.ql-snow .ql-editor h2{font-size:1.5em}.ql-snow .ql-editor h3{font-size:1.17em}.ql-snow .ql-editor h4{font-size:1em}.ql-snow .ql-editor h5{font-size:.83em}.ql-snow .ql-editor h6{font-size:.67em}.ql-snow .ql-editor a{text-decoration:underline}.ql-snow .ql-editor blockquote{border-left:4px solid #ccc;margin-bottom:5px;margin-top:5px;padding-left:16px}.ql-snow .ql-editor code,.ql-snow .ql-editor pre{background-color:#f0f0f0;border-radius:3px}.ql-snow .ql-editor pre{white-space:pre-wrap;margin-bottom:5px;margin-top:5px;padding:5px 10px}.ql-snow .ql-editor code{font-size:85%;padding:2px 4px}.ql-snow .ql-editor pre.ql-syntax{background-color:#23241f;color:#f8f8f2;overflow:visible}.ql-snow .ql-editor img{max-width:100%}.ql-snow .ql-picker{color:#444;display:inline-block;float:left;font-size:14px;font-weight:500;height:24px;position:relative;vertical-align:middle}.ql-snow .ql-picker-label{cursor:pointer;display:inline-block;height:100%;padding-left:8px;padding-right:2px;position:relative;width:100%}.ql-snow .ql-picker-label::before{display:inline-block;line-height:22px}.ql-snow .ql-picker-options{background-color:#fff;display:none;min-width:100%;padding:4px 8px;position:absolute;white-space:nowrap}.ql-snow .ql-picker-options .ql-picker-item{cursor:pointer;display:block;padding-bottom:5px;padding-top:5px}.ql-snow .ql-picker.ql-expanded .ql-picker-label{color:#ccc;z-index:2}.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill{fill:#ccc}.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke{stroke:#ccc}.ql-snow .ql-picker.ql-expanded .ql-picker-options{display:block;margin-top:-1px;top:100%;z-index:1}.ql-snow .ql-color-picker,.ql-snow .ql-icon-picker{width:28px}.ql-snow .ql-color-picker .ql-picker-label,.ql-snow .ql-icon-picker .ql-picker-label{padding:2px 4px}.ql-snow .ql-color-picker .ql-picker-label svg,.ql-snow .ql-icon-picker .ql-picker-label svg{right:4px}.ql-snow .ql-icon-picker .ql-picker-options{padding:4px 0}.ql-snow .ql-icon-picker .ql-picker-item{height:24px;width:24px;padding:2px 4px}.ql-snow .ql-color-picker .ql-picker-options{padding:3px 5px;width:152px}.ql-snow .ql-color-picker .ql-picker-item{border:1px solid transparent;float:left;height:16px;margin:2px;padding:0;width:16px}.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg{position:absolute;margin-top:-9px;right:0;top:50%;width:18px}.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before,.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before{content:attr(data-label)}.ql-snow .ql-picker.ql-header{width:98px}.ql-snow .ql-picker.ql-header .ql-picker-item::before,.ql-snow .ql-picker.ql-header .ql-picker-label::before{content:'Normal'}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before{content:'Heading 1'}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before{content:'Heading 2'}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before{content:'Heading 3'}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before{content:'Heading 4'}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before{content:'Heading 5'}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before{content:'Heading 6'}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before{font-size:2em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before{font-size:1.5em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before{font-size:1.17em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before{font-size:1em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before{font-size:.83em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before{font-size:.67em}.ql-snow .ql-picker.ql-font{width:108px}.ql-snow .ql-picker.ql-font .ql-picker-item::before,.ql-snow .ql-picker.ql-font .ql-picker-label::before{content:'Sans Serif'}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before,.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before{content:'Serif'}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before,.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before{content:'Monospace'}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before{font-family:Georgia,Times New Roman,serif}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before{font-family:Monaco,Courier New,monospace}.ql-snow .ql-picker.ql-size{width:98px}.ql-snow .ql-picker.ql-size .ql-picker-item::before,.ql-snow .ql-picker.ql-size .ql-picker-label::before{content:'Normal'}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before{content:'Small'}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before{content:'Large'}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before{content:'Huge'}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before{font-size:10px}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before{font-size:18px}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before{font-size:32px}.ql-snow .ql-color-picker.ql-background .ql-picker-item{background-color:#fff}.ql-snow .ql-color-picker.ql-color .ql-picker-item{background-color:#000}.ql-toolbar.ql-snow{border:1px solid #ccc;box-sizing:border-box;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:8px}.ql-toolbar.ql-snow .ql-formats{margin-right:15px}.ql-toolbar.ql-snow .ql-picker-label{border:1px solid transparent}.ql-toolbar.ql-snow .ql-picker-options{border:1px solid transparent;box-shadow:rgba(0,0,0,.2) 0 2px 8px}.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label{border-color:#ccc}.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options{border-color:#ccc}.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover{border-color:#000}.ql-toolbar.ql-snow+.ql-container.ql-snow{border-top:0}.ql-snow .ql-tooltip{background-color:#fff;border:1px solid #ccc;box-shadow:0 0 5px #ddd;color:#444;padding:5px 12px;white-space:nowrap}.ql-snow .ql-tooltip::before{content:"Visit URL:";line-height:26px;margin-right:8px}.ql-snow .ql-tooltip input[type=text]{display:none;border:1px solid #ccc;font-size:13px;height:26px;margin:0;padding:3px 5px;width:170px}.ql-snow .ql-tooltip a.ql-preview{display:inline-block;max-width:200px;overflow-x:hidden;text-overflow:ellipsis;vertical-align:top}.ql-snow .ql-tooltip a.ql-action::after{border-right:1px solid #ccc;content:'Edit';margin-left:16px;padding-right:8px}.ql-snow .ql-tooltip a.ql-remove::before{content:'Remove';margin-left:8px}.ql-snow .ql-tooltip a{line-height:26px}.ql-snow .ql-tooltip.ql-editing a.ql-preview,.ql-snow .ql-tooltip.ql-editing a.ql-remove{display:none}.ql-snow .ql-tooltip.ql-editing input[type=text]{display:inline-block}.ql-snow .ql-tooltip.ql-editing a.ql-action::after{border-right:0;content:'Save';padding-right:0}.ql-snow .ql-tooltip[data-mode=link]::before{content:"Enter link:"}.ql-snow .ql-tooltip[data-mode=formula]::before{content:"Enter formula:"}.ql-snow .ql-tooltip[data-mode=video]::before{content:"Enter video:"}.ql-snow a{color:#06c}.ql-container.ql-snow{border:1px solid #ccc} +/*# sourceMappingURL=quill.snow.min.css.map */ \ No newline at end of file diff --git a/server/API_STANDARD.md b/server/API_STANDARD.md index 05ee15c4..edd75f24 100644 --- a/server/API_STANDARD.md +++ b/server/API_STANDARD.md @@ -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 with two images ' +position = 1 +topicId = 1 +images = 2 +image_0 = +image_1 = +``` + +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.** diff --git a/server/Makefile b/server/Makefile index 37d92839..e9d473c1 100644 --- a/server/Makefile +++ b/server/Makefile @@ -37,3 +37,6 @@ db: sh: @docker exec -it opensupports-srv bash + +doc: + @apidoc -i models/ -i data/ -i libs/ -i controllers/ -o apidoc/ diff --git a/server/_apidoc.js b/server/_apidoc.js deleted file mode 100644 index 15f7c46b..00000000 --- a/server/_apidoc.js +++ /dev/null @@ -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. - */ \ No newline at end of file diff --git a/server/apidoc.json b/server/apidoc.json index 3fece29a..1feb7dab 100644 --- a/server/apidoc.json +++ b/server/apidoc.json @@ -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" } -} \ No newline at end of file +} diff --git a/server/controllers/article/add-topic.php b/server/controllers/article/add-topic.php index 247f3f47..3f4e240d 100755 --- a/server/controllers/article/add-topic.php +++ b/server/controllers/article/add-topic.php @@ -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 * diff --git a/server/controllers/article/add.php b/server/controllers/article/add.php index 8e309e5a..9881affb 100755 --- a/server/controllers/article/add.php +++ b/server/controllers/article/add.php @@ -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 ]); diff --git a/server/controllers/article/delete-topic.php b/server/controllers/article/delete-topic.php index 6e8f2174..e63aac9f 100755 --- a/server/controllers/article/delete-topic.php +++ b/server/controllers/article/delete-topic.php @@ -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 * diff --git a/server/controllers/article/delete.php b/server/controllers/article/delete.php index 0d3ef1f7..49ce44a6 100755 --- a/server/controllers/article/delete.php +++ b/server/controllers/article/delete.php @@ -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 * diff --git a/server/controllers/article/edit-topic.php b/server/controllers/article/edit-topic.php index f60e5c3c..92df3653 100755 --- a/server/controllers/article/edit-topic.php +++ b/server/controllers/article/edit-topic.php @@ -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 * diff --git a/server/controllers/article/edit.php b/server/controllers/article/edit.php index 2f30a62c..b91ba489 100755 --- a/server/controllers/article/edit.php +++ b/server/controllers/article/edit.php @@ -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(); } -} \ No newline at end of file +} diff --git a/server/controllers/article/get-all.php b/server/controllers/article/get-all.php index b3cb39b6..bcd76393 100755 --- a/server/controllers/article/get-all.php +++ b/server/controllers/article/get-all.php @@ -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 * diff --git a/server/controllers/staff/add.php b/server/controllers/staff/add.php index c788cb02..d11c7b5e 100755 --- a/server/controllers/staff/add.php +++ b/server/controllers/staff/add.php @@ -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 * diff --git a/server/controllers/staff/assign-ticket.php b/server/controllers/staff/assign-ticket.php index e6735525..febc65d5 100755 --- a/server/controllers/staff/assign-ticket.php +++ b/server/controllers/staff/assign-ticket.php @@ -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 * diff --git a/server/controllers/staff/delete.php b/server/controllers/staff/delete.php index f2a8c244..033d19a7 100755 --- a/server/controllers/staff/delete.php +++ b/server/controllers/staff/delete.php @@ -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 * diff --git a/server/controllers/staff/edit.php b/server/controllers/staff/edit.php index eb7d8d7e..77ae9b48 100755 --- a/server/controllers/staff/edit.php +++ b/server/controllers/staff/edit.php @@ -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; } diff --git a/server/controllers/staff/get-all-tickets.php b/server/controllers/staff/get-all-tickets.php index a8f9781c..03d2c8bf 100755 --- a/server/controllers/staff/get-all-tickets.php +++ b/server/controllers/staff/get-all-tickets.php @@ -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 * diff --git a/server/controllers/staff/get-all.php b/server/controllers/staff/get-all.php index 0efe1d64..337859ae 100755 --- a/server/controllers/staff/get-all.php +++ b/server/controllers/staff/get-all.php @@ -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 * diff --git a/server/controllers/staff/get-new-tickets.php b/server/controllers/staff/get-new-tickets.php index 76e282b1..c342ac11 100755 --- a/server/controllers/staff/get-new-tickets.php +++ b/server/controllers/staff/get-new-tickets.php @@ -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 * diff --git a/server/controllers/staff/get-tickets.php b/server/controllers/staff/get-tickets.php index 9dd0269c..1f768042 100755 --- a/server/controllers/staff/get-tickets.php +++ b/server/controllers/staff/get-tickets.php @@ -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 * diff --git a/server/controllers/staff/get.php b/server/controllers/staff/get.php index 8b1e6793..85b7eacf 100755 --- a/server/controllers/staff/get.php +++ b/server/controllers/staff/get.php @@ -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 * diff --git a/server/controllers/staff/last-events.php b/server/controllers/staff/last-events.php index f915e375..970276a1 100755 --- a/server/controllers/staff/last-events.php +++ b/server/controllers/staff/last-events.php @@ -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 * diff --git a/server/controllers/staff/search-tickets.php b/server/controllers/staff/search-tickets.php index 6ac6cf3c..783e6424 100755 --- a/server/controllers/staff/search-tickets.php +++ b/server/controllers/staff/search-tickets.php @@ -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 * diff --git a/server/controllers/staff/un-assign-ticket.php b/server/controllers/staff/un-assign-ticket.php index 61866277..537e6cff 100755 --- a/server/controllers/staff/un-assign-ticket.php +++ b/server/controllers/staff/un-assign-ticket.php @@ -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 * diff --git a/server/controllers/system/add-api-key.php b/server/controllers/system/add-api-key.php index cc6234f9..62477979 100755 --- a/server/controllers/system/add-api-key.php +++ b/server/controllers/system/add-api-key.php @@ -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 * diff --git a/server/controllers/system/add-department.php b/server/controllers/system/add-department.php index 58cd7c06..be0ed503 100755 --- a/server/controllers/system/add-department.php +++ b/server/controllers/system/add-department.php @@ -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 * diff --git a/server/controllers/system/backup-database.php b/server/controllers/system/backup-database.php index b1253252..df52f109 100755 --- a/server/controllers/system/backup-database.php +++ b/server/controllers/system/backup-database.php @@ -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 * diff --git a/server/controllers/system/check-requirements.php b/server/controllers/system/check-requirements.php index 99bd57dd..4e732c5c 100755 --- a/server/controllers/system/check-requirements.php +++ b/server/controllers/system/check-requirements.php @@ -2,7 +2,7 @@ /** * @api {post} /system/check-requirements Checks requirements - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Check requirements * diff --git a/server/controllers/system/csv-import.php b/server/controllers/system/csv-import.php index 73fb2bb3..8a348a3e 100755 --- a/server/controllers/system/csv-import.php +++ b/server/controllers/system/csv-import.php @@ -2,7 +2,7 @@ /** * @api {post} /system/csv-import CSV import - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName CSV import * diff --git a/server/controllers/system/delete-all-users.php b/server/controllers/system/delete-all-users.php index 2dd6902d..db8c83b1 100755 --- a/server/controllers/system/delete-all-users.php +++ b/server/controllers/system/delete-all-users.php @@ -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 * diff --git a/server/controllers/system/delete-api-key.php b/server/controllers/system/delete-api-key.php index 86ffed5a..43b179e6 100755 --- a/server/controllers/system/delete-api-key.php +++ b/server/controllers/system/delete-api-key.php @@ -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 * diff --git a/server/controllers/system/delete-department.php b/server/controllers/system/delete-department.php index 17b4ac38..0c44792d 100755 --- a/server/controllers/system/delete-department.php +++ b/server/controllers/system/delete-department.php @@ -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 * diff --git a/server/controllers/system/disable-registration.php b/server/controllers/system/disable-registration.php index 2de734d8..777bb461 100755 --- a/server/controllers/system/disable-registration.php +++ b/server/controllers/system/disable-registration.php @@ -2,7 +2,7 @@ /** * @api {post} /system/disable-registration Disable registration - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Disable registration * diff --git a/server/controllers/system/disable-user-system.php b/server/controllers/system/disable-user-system.php index 16d36906..f3b5cfd3 100755 --- a/server/controllers/system/disable-user-system.php +++ b/server/controllers/system/disable-user-system.php @@ -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 * diff --git a/server/controllers/system/download.php b/server/controllers/system/download.php index 7ad437f4..663ff51e 100755 --- a/server/controllers/system/download.php +++ b/server/controllers/system/download.php @@ -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']; } } diff --git a/server/controllers/system/edit-department.php b/server/controllers/system/edit-department.php index a03b85f4..19de4de8 100755 --- a/server/controllers/system/edit-department.php +++ b/server/controllers/system/edit-department.php @@ -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 * diff --git a/server/controllers/system/edit-mail-template.php b/server/controllers/system/edit-mail-template.php index 6a76166b..8663538e 100755 --- a/server/controllers/system/edit-mail-template.php +++ b/server/controllers/system/edit-mail-template.php @@ -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 * diff --git a/server/controllers/system/edit-settings.php b/server/controllers/system/edit-settings.php index 5aa92802..3a47a027 100755 --- a/server/controllers/system/edit-settings.php +++ b/server/controllers/system/edit-settings.php @@ -2,7 +2,7 @@ /** * @api {post} /system/edit-settings Edit settings - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Edit settings * diff --git a/server/controllers/system/enable-registration.php b/server/controllers/system/enable-registration.php index c39e33b5..7f568d48 100755 --- a/server/controllers/system/enable-registration.php +++ b/server/controllers/system/enable-registration.php @@ -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 * diff --git a/server/controllers/system/enable-user-system.php b/server/controllers/system/enable-user-system.php index 83c4c3ba..22cc4237 100755 --- a/server/controllers/system/enable-user-system.php +++ b/server/controllers/system/enable-user-system.php @@ -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 * diff --git a/server/controllers/system/get-api-keys.php b/server/controllers/system/get-api-keys.php index 870a69dc..8b01e04e 100755 --- a/server/controllers/system/get-api-keys.php +++ b/server/controllers/system/get-api-keys.php @@ -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 * diff --git a/server/controllers/system/get-logs.php b/server/controllers/system/get-logs.php index 9ef33f21..44fb4896 100755 --- a/server/controllers/system/get-logs.php +++ b/server/controllers/system/get-logs.php @@ -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 * diff --git a/server/controllers/system/get-mail-templates.php b/server/controllers/system/get-mail-templates.php index 85f9f3ae..8dfbffa5 100755 --- a/server/controllers/system/get-mail-templates.php +++ b/server/controllers/system/get-mail-templates.php @@ -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 * diff --git a/server/controllers/system/get-settings.php b/server/controllers/system/get-settings.php index 53489d78..5dcd636b 100755 --- a/server/controllers/system/get-settings.php +++ b/server/controllers/system/get-settings.php @@ -2,7 +2,7 @@ /** * @api {post} /system/get-settings Get settings - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Get settings * diff --git a/server/controllers/system/get-stats.php b/server/controllers/system/get-stats.php index 7dbc7c04..4f432cbb 100755 --- a/server/controllers/system/get-stats.php +++ b/server/controllers/system/get-stats.php @@ -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 * diff --git a/server/controllers/system/init-admin.php b/server/controllers/system/init-admin.php index 599c354f..7e61d2bb 100755 --- a/server/controllers/system/init-admin.php +++ b/server/controllers/system/init-admin.php @@ -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 * diff --git a/server/controllers/system/init-database.php b/server/controllers/system/init-database.php index 2e16f121..67aefd8c 100755 --- a/server/controllers/system/init-database.php +++ b/server/controllers/system/init-database.php @@ -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 * diff --git a/server/controllers/system/init-settings.php b/server/controllers/system/init-settings.php index 97a87009..ae177b14 100755 --- a/server/controllers/system/init-settings.php +++ b/server/controllers/system/init-settings.php @@ -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'), diff --git a/server/controllers/system/installation-done.php b/server/controllers/system/installation-done.php index af7ba9b9..f1e1eef6 100755 --- a/server/controllers/system/installation-done.php +++ b/server/controllers/system/installation-done.php @@ -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 * diff --git a/server/controllers/system/recover-mail-template.php b/server/controllers/system/recover-mail-template.php index 887cd8e4..73803a37 100755 --- a/server/controllers/system/recover-mail-template.php +++ b/server/controllers/system/recover-mail-template.php @@ -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 * diff --git a/server/controllers/system/test-smtp.php b/server/controllers/system/test-smtp.php index 3d5e36c9..1bd3921b 100644 --- a/server/controllers/system/test-smtp.php +++ b/server/controllers/system/test-smtp.php @@ -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 * diff --git a/server/controllers/ticket/add-custom-response.php b/server/controllers/ticket/add-custom-response.php index ce6f13ac..e8742163 100755 --- a/server/controllers/ticket/add-custom-response.php +++ b/server/controllers/ticket/add-custom-response.php @@ -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 * diff --git a/server/controllers/ticket/change-department.php b/server/controllers/ticket/change-department.php index 036a3120..47021b06 100755 --- a/server/controllers/ticket/change-department.php +++ b/server/controllers/ticket/change-department.php @@ -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 * diff --git a/server/controllers/ticket/change-priority.php b/server/controllers/ticket/change-priority.php index 286d181e..ac7d47b0 100755 --- a/server/controllers/ticket/change-priority.php +++ b/server/controllers/ticket/change-priority.php @@ -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 * diff --git a/server/controllers/ticket/check.php b/server/controllers/ticket/check.php index 1edf1f53..59f1dd8b 100755 --- a/server/controllers/ticket/check.php +++ b/server/controllers/ticket/check.php @@ -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 * diff --git a/server/controllers/ticket/close.php b/server/controllers/ticket/close.php index 1b0d71f6..9cacd543 100755 --- a/server/controllers/ticket/close.php +++ b/server/controllers/ticket/close.php @@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true); /** * @api {post} /ticket/close Close ticket - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Close * diff --git a/server/controllers/ticket/comment.php b/server/controllers/ticket/comment.php index d62ffe54..5b250812 100755 --- a/server/controllers/ticket/comment.php +++ b/server/controllers/ticket/comment.php @@ -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 diff --git a/server/controllers/ticket/create.php b/server/controllers/ticket/create.php index 6af609b5..112f0391 100755 --- a/server/controllers/ticket/create.php +++ b/server/controllers/ticket/create.php @@ -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, diff --git a/server/controllers/ticket/delete-custom-response.php b/server/controllers/ticket/delete-custom-response.php index 204c1dcf..e77dc84b 100755 --- a/server/controllers/ticket/delete-custom-response.php +++ b/server/controllers/ticket/delete-custom-response.php @@ -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 * diff --git a/server/controllers/ticket/edit-custom-response.php b/server/controllers/ticket/edit-custom-response.php index d4bad091..7837a80d 100755 --- a/server/controllers/ticket/edit-custom-response.php +++ b/server/controllers/ticket/edit-custom-response.php @@ -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 * diff --git a/server/controllers/ticket/get-custom-responses.php b/server/controllers/ticket/get-custom-responses.php index 5697efb3..38510f81 100755 --- a/server/controllers/ticket/get-custom-responses.php +++ b/server/controllers/ticket/get-custom-responses.php @@ -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 * diff --git a/server/controllers/ticket/get.php b/server/controllers/ticket/get.php index d1a62b4d..6bc30d2a 100755 --- a/server/controllers/ticket/get.php +++ b/server/controllers/ticket/get.php @@ -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 * diff --git a/server/controllers/ticket/re-open.php b/server/controllers/ticket/re-open.php index 56b1a183..e7956cff 100755 --- a/server/controllers/ticket/re-open.php +++ b/server/controllers/ticket/re-open.php @@ -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 * diff --git a/server/controllers/ticket/seen.php b/server/controllers/ticket/seen.php index 5c40d406..90851994 100755 --- a/server/controllers/ticket/seen.php +++ b/server/controllers/ticket/seen.php @@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator; /** * @api {post} /ticket/seen See ticket - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName See ticket * diff --git a/server/controllers/user.php b/server/controllers/user.php index 40af4c14..7d10b549 100755 --- a/server/controllers/user.php +++ b/server/controllers/user.php @@ -15,6 +15,8 @@ include 'user/ban.php'; include 'user/un-ban.php'; include 'user/list-ban.php'; include 'user/verify.php'; +include 'user/enable.php'; +include 'user/disable.php'; $userControllers = new ControllerGroup(); $userControllers->setGroupPath('/user'); @@ -35,4 +37,6 @@ $userControllers->addController(new BanUserController); $userControllers->addController(new UnBanUserController); $userControllers->addController(new ListBanUserController); $userControllers->addController(new VerifyController); +$userControllers->addController(new EnableUserController); +$userControllers->addController(new DisableUserController); $userControllers->finalize(); diff --git a/server/controllers/user/ban.php b/server/controllers/user/ban.php index 52b72d66..c769070e 100755 --- a/server/controllers/user/ban.php +++ b/server/controllers/user/ban.php @@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator; /** * @api {post} /user/ban Ban email - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Ban email * diff --git a/server/controllers/user/check-session.php b/server/controllers/user/check-session.php index 613815f8..fec277ac 100755 --- a/server/controllers/user/check-session.php +++ b/server/controllers/user/check-session.php @@ -2,7 +2,7 @@ /** * @api {post} /user/check-session Check session - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Check session * diff --git a/server/controllers/user/delete.php b/server/controllers/user/delete.php index 5422e80e..ee24df2e 100755 --- a/server/controllers/user/delete.php +++ b/server/controllers/user/delete.php @@ -4,7 +4,7 @@ use RedBeanPHP\Facade as RedBean; /** * @api {post} /user/delete Delete user - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Delete user * diff --git a/server/controllers/user/disable.php b/server/controllers/user/disable.php new file mode 100644 index 00000000..f135e81b --- /dev/null +++ b/server/controllers/user/disable.php @@ -0,0 +1,52 @@ + 'staff_1', + 'requestData' => [ + 'userId' => [ + 'validation' => DataValidator::dataStoreId('user'), + 'error' => ERRORS::INVALID_USER + ] + ] + ]; + } + + public function handler() { + $user = User::getDataStore(Controller::request('userId')); + if($user->disabled) { + throw new Exception(ERRORS::ALREADY_DISABLED); + } + + $user->disabled = 1; + $user->store(); + + Response::respondSuccess(); + } +} diff --git a/server/controllers/user/edit-email.php b/server/controllers/user/edit-email.php index cd8e188a..b8003402 100755 --- a/server/controllers/user/edit-email.php +++ b/server/controllers/user/edit-email.php @@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator; /** * @api {post} /user/edit-email Edit email - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Edit email * diff --git a/server/controllers/user/edit-password.php b/server/controllers/user/edit-password.php index 758c254d..ef46c8b7 100755 --- a/server/controllers/user/edit-password.php +++ b/server/controllers/user/edit-password.php @@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator; /** * @api {post} /user/edit-password Edit password - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Edit password * diff --git a/server/controllers/user/enable.php b/server/controllers/user/enable.php new file mode 100644 index 00000000..d870970a --- /dev/null +++ b/server/controllers/user/enable.php @@ -0,0 +1,53 @@ + 'staff_1', + 'requestData' => [ + 'userId' => [ + 'validation' => DataValidator::dataStoreId('user'), + 'error' => ERRORS::INVALID_USER + ] + ] + ]; + } + + public function handler() { + $user = User::getDataStore(Controller::request('userId')); + + if(!$user->disabled) { + throw new Exception(ERRORS::ALREADY_ENABLED); + } + + $user->disabled = 0; + $user->store(); + + Response::respondSuccess(); + } +} diff --git a/server/controllers/user/get-user.php b/server/controllers/user/get-user.php index 812250e9..de56b46b 100755 --- a/server/controllers/user/get-user.php +++ b/server/controllers/user/get-user.php @@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true); /** * @api {post} /user/get-user Get user information - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Get user information * @@ -46,17 +46,16 @@ class GetUserByIdController extends Controller { } public function handler() { - if(!Controller::isUserSystemEnabled()) { throw new Exception(ERRORS::USER_SYSTEM_DISABLED); } - + $userId = Controller::request('userId'); $user = User::getDataStore($userId); $staff = Controller::getLoggedUser(); - + $tickets = new DataStoreList(); - + foreach($user->sharedTicketList as $ticket) { if($staff->sharedDepartmentList->includesId($ticket->department->id)) { $tickets->add($ticket); @@ -68,7 +67,8 @@ class GetUserByIdController extends Controller { 'email' => $user->email, 'signupDate' => $user->signupDate, 'tickets' => $tickets->toArray(), - 'verified' => !$user->verificationToken + 'verified' => !$user->verificationToken, + 'disabled' => !!$user->disabled ]); } -} \ No newline at end of file +} diff --git a/server/controllers/user/get-users.php b/server/controllers/user/get-users.php index 5c53a796..b709206e 100755 --- a/server/controllers/user/get-users.php +++ b/server/controllers/user/get-users.php @@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator; /** * @api {post} /user/get-users Get users list - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Get users list * @@ -15,7 +15,7 @@ use Respect\Validation\Validator as DataValidator; * * @apiParam {Number} page Number of pages of users. * @apiParam {String} orderBy Parameter to order the users by tickets or id. - * @apiParam {Boolean} desc Parameter to order the users in an ascending or descending way. + * @apiParam {Boolean} desc Parameter to order the users in an ascending or descending way. * @apiParam {String} search Text query to find users. * * @apiUse NO_PERMISSION @@ -56,7 +56,7 @@ class GetUsersController extends Controller { if(!Controller::isUserSystemEnabled()) { throw new Exception(ERRORS::USER_SYSTEM_DISABLED); } - + $userList = $this->getUserList(); $userListArray = []; @@ -67,7 +67,8 @@ class GetUsersController extends Controller { 'verified' => !$user->verificationToken, 'tickets' => $user->tickets, 'email' => $user->email, - 'signupDate' => $user->signupDate + 'signupDate' => $user->signupDate, + 'disabled' => !!$user->disabled ]; } @@ -141,4 +142,4 @@ class GetUsersController extends Controller { return $query; } -} \ No newline at end of file +} diff --git a/server/controllers/user/get.php b/server/controllers/user/get.php index f85d4527..f871a870 100755 --- a/server/controllers/user/get.php +++ b/server/controllers/user/get.php @@ -4,7 +4,7 @@ DataValidator::with('CustomValidations', true); /** * @api {post} /user/get Get my information - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Get my Information * diff --git a/server/controllers/user/list-ban.php b/server/controllers/user/list-ban.php index 1f625391..5c3ba684 100755 --- a/server/controllers/user/list-ban.php +++ b/server/controllers/user/list-ban.php @@ -3,7 +3,7 @@ use Respect\Validation\Validator as DataValidator; /** * @api {post} /user/list-ban Get ban list - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Get ban list * diff --git a/server/controllers/user/login.php b/server/controllers/user/login.php index 14c2ffe3..28b5c45d 100755 --- a/server/controllers/user/login.php +++ b/server/controllers/user/login.php @@ -2,7 +2,7 @@ /** * @api {post} /user/login Login - * @apiVersion 4.2.0 + * @apiVersion 4.3.0 * * @apiName Login * @@ -61,6 +61,10 @@ class LoginController extends Controller { throw new Exception(ERRORS::UNVERIFIED_USER); } + if($this->userInstance->disabled) { + throw new Exception(ERRORS::USER_DISABLED); + } + $this->createUserSession(); $this->createSessionCookie(); if(Controller::request('staff')) { diff --git a/server/controllers/user/logout.php b/server/controllers/user/logout.php index eb9dc091..e3e38f23 100755 --- a/server/controllers/user/logout.php +++ b/server/controllers/user/logout.php @@ -1,7 +1,7 @@ verificationToken !== $token) { - Response::respondError(ERRORS::INVALID_TOKEN); - return; + throw new Exception(ERRORS::INVALID_TOKEN); } + $userRow->verificationToken = null; $userRow->store(); - + Response::respondSuccess(); } -} \ No newline at end of file +} diff --git a/server/data/ERRORS.php b/server/data/ERRORS.php index 1ce5c219..5776d831 100755 --- a/server/data/ERRORS.php +++ b/server/data/ERRORS.php @@ -161,7 +161,7 @@ */ /** * @apiDefine INVALID_FILE - * @apiError {String} INVALID_FILE The file is invalid. + * @apiError {String} INVALID_FILE The file is invalid or max size exceeded. */ /** * @apiDefine DATABASE_CONNECTION @@ -175,6 +175,18 @@ * @apiDefine SMTP_CONNECTION * @apiError {String} SMTP_CONNECTION Could not connect with SMTP server. */ +/** + * @apiDefine ALREADY_DISABLED + * @apiError {String} ALREADY_DISABLED User is already disabled + */ +/** + * @apiDefine ALREADY_ENABLED + * @apiError {String} ALREADY_ENABLED User is already enabled + */ +/** + * @apiDefine USER_DISABLED + * @apiError {String} USER_DISABLED User is disabled + */ class ERRORS { const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS'; @@ -222,4 +234,7 @@ class ERRORS { const DATABASE_CONNECTION = 'DATABASE_CONNECTION'; const DATABASE_CREATION = 'DATABASE_CREATION'; const SMTP_CONNECTION = 'SMTP_CONNECTION'; + const ALREADY_DISABLED = 'ALREADY_DISABLED'; + const ALREADY_ENABLED = 'ALREADY_ENABLED'; + const USER_DISABLED = 'USER_DISABLED'; } diff --git a/server/libs/Controller.php b/server/libs/Controller.php index d84ca066..83c126f2 100755 --- a/server/libs/Controller.php +++ b/server/libs/Controller.php @@ -95,10 +95,47 @@ abstract class Controller { return \Slim\Slim::getInstance(); } + public function uploadImages($forceUpload = false) { + $allowAttachments = Setting::getSetting('allow-attachments')->getValue(); + $totalImages = Controller::request('images') * 1; + + if(!$allowAttachments && !$forceUpload) return []; + if(!$totalImages) return []; + + $maxSize = Setting::getSetting('max-size')->getValue(); + $fileGap = Setting::getSetting('file-gap')->getValue(); + $fileFirst = Setting::getSetting('file-first-number')->getValue(); + $fileQuantity = Setting::getSetting('file-quantity'); + + $fileUploader = FileUploader::getInstance(); + $fileUploader->setMaxSize($maxSize); + + $allImagesValidSize = true; + + for($i=0;$i<$totalImages;$i++) { + $allImagesValidSize = $allImagesValidSize && $fileUploader->isSizeValid($_FILES["image_$i"]); + } + + if(!$allImagesValidSize) throw new Exception(ERRORS::INVALID_FILE); + + $imagePaths = []; + $url = Setting::getSetting('url')->getValue(); + for($i=0;$i<$totalImages;$i++) { + $fileUploader->setGeneratorValues($fileGap, $fileFirst, $fileQuantity->getValue()); + $fileUploader->upload($_FILES["image_$i"]); + $imagePaths[] = $url . '/api/system/download?file=' . $fileUploader->getFileName(); + $fileQuantity->value++; + } + + $fileQuantity->store(); + return $imagePaths; + } + public function uploadFile($forceUpload = false) { $allowAttachments = Setting::getSetting('allow-attachments')->getValue(); - if(!isset($_FILES['file']) || (!$allowAttachments && !$forceUpload)) return ''; + if(!$allowAttachments && !$forceUpload) return ''; + if(!isset($_FILES['file'])) return ''; $maxSize = Setting::getSetting('max-size')->getValue(); $fileGap = Setting::getSetting('file-gap')->getValue(); @@ -119,6 +156,11 @@ abstract class Controller { } } + public function replaceWithImagePaths($imagePaths, $content) { + if(!is_array($imagePaths)) return $content; + return str_replace(array_map(function($index) { return "IMAGE_PATH_$index"; }, array_keys($imagePaths)), $imagePaths, $content); + } + public static function isUserSystemEnabled() { return Setting::getSetting('user-system-enabled')->getValue(); } diff --git a/server/libs/FileDownloader.php b/server/libs/FileDownloader.php index f8bc7435..3d069029 100755 --- a/server/libs/FileDownloader.php +++ b/server/libs/FileDownloader.php @@ -51,4 +51,32 @@ class FileDownloader extends FileManager { array_key_exists($fileExtension, $contentTypes) ? $fileExtension : 'default' ]; } + + public function getFilePermission() { + if(!strlen($this->getFileName())) return NULL; + $indicator = $this->getFileName()[0]; + + if($indicator === 'a') { + return FileManager::PERMISSION_ARTICLE; + } else if($indicator === 't') { + return FileManager::PERMISSION_TICKET; + } else if($indicator === 'p') { + return FileManager::PERMISSION_PROFILE; + } + + return NULL; + } + + public function getTicketNumber() { + $fileName = $this->getFileName(); + if(strlen($fileName) < 2) return NULL; + $ticketNumber = 0; + + for($i=1; $fileName[$i] !== '_'; $i++) { + $ticketNumber *= 10; + $ticketNumber += $fileName[$i]; + } + + return $ticketNumber; + } } diff --git a/server/libs/FileManager.php b/server/libs/FileManager.php index fd05bb13..d1b9f147 100755 --- a/server/libs/FileManager.php +++ b/server/libs/FileManager.php @@ -3,7 +3,11 @@ abstract class FileManager { private $fileName; private $localPath = 'files/'; - + + const PERMISSION_ARTICLE = 'PERMISSION_ARTICLE'; + const PERMISSION_TICKET = 'PERMISSION_TICKET'; + const PERMISSION_PROFILE = 'PERMISSION_PROFILE'; + public function setLocalPath($localPath) { $this->localPath = $localPath; } @@ -19,8 +23,8 @@ abstract class FileManager { public function getFileName() { return $this->fileName; } - + public function getFullFilePath() { return $this->getLocalPath() . $this->getFileName(); } -} \ No newline at end of file +} diff --git a/server/libs/FileUploader.php b/server/libs/FileUploader.php index d078927e..71048155 100755 --- a/server/libs/FileUploader.php +++ b/server/libs/FileUploader.php @@ -1,10 +1,11 @@ maxSize); + } + public function upload($file) { $this->setNewName($file['name']); - if($file['size'] > (1024 * $this->maxSize)) { + if(!$this->isSizeValid($file)) { return false; } - + move_uploaded_file($file['tmp_name'], $this->getLocalPath() . $this->getFileName()); return true; @@ -36,10 +41,18 @@ class FileUploader extends FileManager { $newName = preg_replace('/[^a-zA-Z0-9\d\.\-]/', '_', $newName); if ($this->linearCongruentialGenerator instanceof LinearCongruentialGenerator) { - $newName = $this->linearCongruentialGenerator->generate($this->linearCongruentialGeneratorOffset) . '_' . $newName; - } + if($this->permission) $this->fileName = $this->permission . '_'; + else $this->fileName = ''; - $this->fileName = $newName; + $this->fileName .= $this->linearCongruentialGenerator->generate($this->linearCongruentialGeneratorOffset) . '_' . $newName; + } + } + + public function setPermission($type = '', $extra = '') { + if($type === FileManager::PERMISSION_ARTICLE) $this->permission = 'a'; + else if($type === FileManager::PERMISSION_TICKET) $this->permission = 't' . $extra; + else if($type === FileManager::PERMISSION_PROFILE) $this->permission = 'p'; + else $this->permission = ''; } public function setGeneratorValues($gap, $first, $offset) { @@ -49,13 +62,12 @@ class FileUploader extends FileManager { $this->linearCongruentialGenerator->setGap($gap); $this->linearCongruentialGenerator->setFirst($first); } - + public function setMaxSize($maxSize) { $this->maxSize = $maxSize; } - + public function getFileName() { return $this->fileName; } - -} \ No newline at end of file +} diff --git a/server/models/APIKey.php b/server/models/APIKey.php index 21f8e5da..a02e9887 100755 --- a/server/models/APIKey.php +++ b/server/models/APIKey.php @@ -1,7 +1,7 @@ authorToArray(); + if(is_string($user)) return $user == $ticketAuthor['email']; return $user->id == $ticketAuthor['id'] && ($user instanceof Staff) == $ticketAuthor['staff']; } diff --git a/server/models/Ticketevent.php b/server/models/Ticketevent.php index a8887e6a..d76c6a69 100755 --- a/server/models/Ticketevent.php +++ b/server/models/Ticketevent.php @@ -1,7 +1,7 @@ $this->email, 'id' => $this->id, 'name' => $this->name, - 'verified' => !$this->verificationToken + 'verified' => !$this->verificationToken, + 'disabled' => $this->disabled ]; } } diff --git a/tests/init.rb b/tests/init.rb index 0b853b13..04356833 100644 --- a/tests/init.rb +++ b/tests/init.rb @@ -19,6 +19,7 @@ require './user/recover-password.rb' require './user/edit-password.rb' require './user/edit-email.rb' require './user/get.rb' +require './user/enable-disable.rb' require './ticket/create.rb' require './ticket/comment.rb' require './ticket/get.rb' diff --git a/tests/system/disable-user-system.rb b/tests/system/disable-user-system.rb index 210c45f5..81884ea8 100644 --- a/tests/system/disable-user-system.rb +++ b/tests/system/disable-user-system.rb @@ -19,7 +19,7 @@ describe'system/disable-user-system' do numberOftickets= $database.query("SELECT * FROM ticket WHERE author_id IS NULL AND author_email IS NOT NULL AND author_name IS NOT NULL") - (numberOftickets.num_rows).should.equal(38) + (numberOftickets.num_rows).should.equal(39) request('/user/logout') @@ -93,7 +93,7 @@ describe'system/disable-user-system' do numberOftickets= $database.query("SELECT * FROM ticket WHERE author_email IS NULL AND author_name IS NULL AND author_id IS NOT NULL" ) - (numberOftickets.num_rows).should.equal(39) + (numberOftickets.num_rows).should.equal(40) end diff --git a/tests/system/edit-settings.rb b/tests/system/edit-settings.rb index 73684b26..5923d364 100644 --- a/tests/system/edit-settings.rb +++ b/tests/system/edit-settings.rb @@ -10,7 +10,7 @@ describe'system/edit-settings' do "time-zone" => -3, "layout" => 'full-width', "allow-attachments" => 1, - "max-size" => 2048, + "max-size" => 2, "language" => 'en', "no-reply-email" => 'testemail@hotmail.com' }) @@ -27,7 +27,7 @@ describe'system/edit-settings' do (row['value']).should.equal('full-width') row = $database.getRow('setting', 'max-size', 'name') - (row['value']).should.equal('2048') + (row['value']).should.equal('2') row = $database.getRow('setting', 'language', 'name') (row['value']).should.equal('en') diff --git a/tests/system/file-upload-download.rb b/tests/system/file-upload-download.rb index 42da8593..b8ceef02 100644 --- a/tests/system/file-upload-download.rb +++ b/tests/system/file-upload-download.rb @@ -21,6 +21,7 @@ describe 'File Upload and Download' do ticket = $database.getLastRow('ticket') (ticket['file'].include? 'upload_3_.txt').should.equal(true) + (ticket['file'].include? ('' + ticket['ticket_number'] + '_')).should.equal(true) (File.exist? ('../server/files/' + ticket['file'])).should.equal(true) end @@ -66,6 +67,7 @@ describe 'File Upload and Download' do }) user = $database.getRow('staff', $csrf_userid) + (user['profile_pic'][0] == 'p').should.equal(true) result = plainRequest('/system/download', { 'csrf_userid' => $csrf_userid, @@ -75,4 +77,29 @@ describe 'File Upload and Download' do (result.body).should.include('file content') end + + it 'should add images to ticket content when creating a new ticket' do + request('/user/logout') + Scripts.login('creator@os4.com', 'creator') + + file = File.open( "../server/files/profile.jpg") + + result = request('/ticket/create', { + 'csrf_userid' => $csrf_userid, + 'csrf_token' => $csrf_token, + 'title' => 'Ticket with file', + 'content' => 'this is a ticket that contains images ', + 'language' => 'en', + 'departmentId' => 1, + 'images' => 2, + 'image_0' => File.open( "../server/files/profile.jpg"), + 'image_1' => File.open( "../server/files/profile.jpg"), + }) + + (result['status']).should.equal('success') + + ticket = $database.getLastRow('ticket') + + (ticket['content'].include? '_profile.jpg').should.equal(true) + end end diff --git a/tests/user/enable-disable.rb b/tests/user/enable-disable.rb new file mode 100644 index 00000000..3b289362 --- /dev/null +++ b/tests/user/enable-disable.rb @@ -0,0 +1,75 @@ +describe 'Enable/disable user' do + user = $database.getRow('user', 'login@os4.com', 'email'); + + describe '/user/disable' do + request('/user/logout'); + Scripts.login('staff@opensupports.com', 'staff', true); + + it 'should disable user' do + result = request('/user/disable', { + userId: user['id'], + csrf_token: $csrf_token, + csrf_userid: $csrf_userid, + }) + + (result['status']).should.equal('success'); + end + + it 'should not disable user if already disabled' do + result = request('/user/disable', { + userId: user['id'], + csrf_token: $csrf_token, + csrf_userid: $csrf_userid, + }) + + (result['status']).should.equal('fail'); + (result['message']).should.equal('ALREADY_DISABLED') + end + + it 'should reject login' do + request('/user/logout'); + result = request('/user/login', { + email: 'login@os4.com', + password: 'loginpass' + }) + + (result['status']).should.equal('fail') + (result['message']).should.equal('USER_DISABLED') + end + end + + describe '/user/enable' do + request('/user/logout'); + Scripts.login('staff@opensupports.com', 'staff', true); + + it 'should enable user' do + result = request('/user/enable', { + userId: user['id'], + csrf_token: $csrf_token, + csrf_userid: $csrf_userid, + }) + + (result['status']).should.equal('success'); + end + + it 'should not enable user if already enabled' do + result = request('/user/enable', { + userId: user['id'], + csrf_token: $csrf_token, + csrf_userid: $csrf_userid, + }) + + (result['status']).should.equal('fail'); + (result['message']).should.equal('ALREADY_ENABLED') + + result = request('/user/enable', { + userId: 1, + csrf_token: $csrf_token, + csrf_userid: $csrf_userid, + }) + + (result['status']).should.equal('fail'); + (result['message']).should.equal('ALREADY_ENABLED') + end + end +end