mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-28 16:24:42 +02:00
Merge branch 'master' into OS156-Disable-user-system
Conflicts: server/controllers/system.php server/controllers/system/init-settings.php server/data/ERRORS.php server/libs/Controller.php tests/init.rb
This commit is contained in:
commit
1c0c16f7f6
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
|
import API from 'lib-app/api-call';
|
||||||
|
|
||||||
import DateTransformer from 'lib-core/date-transformer';
|
import DateTransformer from 'lib-core/date-transformer';
|
||||||
import Icon from 'core-components/icon';
|
import Icon from 'core-components/icon';
|
||||||
@ -161,7 +162,7 @@ class TicketEvent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-viewer__file">
|
<div className="ticket-event__file">
|
||||||
{node}
|
{node}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -222,9 +223,26 @@ class TicketEvent extends React.Component {
|
|||||||
const fileName = filePath.replace(/^.*[\\\/]/, '');
|
const fileName = filePath.replace(/^.*[\\\/]/, '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={filePath} target="_blank">{fileName}</a>
|
<span onClick={this.onFileClick.bind(this, filePath)}>{fileName}</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onFileClick(filePath) {
|
||||||
|
API.call({
|
||||||
|
path: '/system/download',
|
||||||
|
plain: true,
|
||||||
|
data: {
|
||||||
|
file: filePath
|
||||||
|
}
|
||||||
|
}).then((result) => {
|
||||||
|
let contentType = 'application/octet-stream';
|
||||||
|
let link = document.createElement('a');
|
||||||
|
let blob = new Blob([result], {'type': contentType});
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = filePath;
|
||||||
|
link.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TicketEvent;
|
export default TicketEvent;
|
||||||
|
@ -91,6 +91,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__file {
|
||||||
|
background-color: $very-light-grey;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: right;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
&_staff {
|
&_staff {
|
||||||
.ticket-event__icon {
|
.ticket-event__icon {
|
||||||
background-color: $primary-blue;
|
background-color: $primary-blue;
|
||||||
|
@ -190,6 +190,7 @@ class TicketViewer extends React.Component {
|
|||||||
<div className="ticket-viewer__response-field row">
|
<div className="ticket-viewer__response-field row">
|
||||||
<Form {...this.getCommentFormProps()}>
|
<Form {...this.getCommentFormProps()}>
|
||||||
<FormField name="content" validation="TEXT_AREA" required field="textarea" />
|
<FormField name="content" validation="TEXT_AREA" required field="textarea" />
|
||||||
|
<FormField name="file" field="file"/>
|
||||||
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
|
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
@ -234,10 +235,12 @@ class TicketViewer extends React.Component {
|
|||||||
loading: this.state.loading,
|
loading: this.state.loading,
|
||||||
onChange: (formState) => {this.setState({
|
onChange: (formState) => {this.setState({
|
||||||
commentValue: formState.content,
|
commentValue: formState.content,
|
||||||
|
commentFile: formState.file,
|
||||||
commentEdited: true
|
commentEdited: true
|
||||||
})},
|
})},
|
||||||
values: {
|
values: {
|
||||||
'content': this.state.commentValue
|
'content': this.state.commentValue,
|
||||||
|
'file': this.state.commentFile
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -48,13 +48,6 @@
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__file {
|
|
||||||
background-color: $very-light-grey;
|
|
||||||
text-align: right;
|
|
||||||
padding: 5px 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__comments {
|
&__comments {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,9 @@ class CreateTicketForm extends React.Component {
|
|||||||
}}/>
|
}}/>
|
||||||
</div>
|
</div>
|
||||||
<FormField label={i18n('CONTENT')} name="content" validation="TEXT_AREA" required field="textarea" />
|
<FormField label={i18n('CONTENT')} name="content" validation="TEXT_AREA" required field="textarea" />
|
||||||
|
<div className="create-ticket-form__file">
|
||||||
|
<FormField name="file" field="file" />
|
||||||
|
</div>
|
||||||
{(!this.props.userLogged) ? this.renderCaptcha() : null}
|
{(!this.props.userLogged) ? this.renderCaptcha() : null}
|
||||||
<SubmitButton>Create Ticket</SubmitButton>
|
<SubmitButton>Create Ticket</SubmitButton>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
.create-ticket-form {
|
.create-ticket-form {
|
||||||
|
|
||||||
|
&__file {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
&__message {
|
&__message {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
40
client/src/core-components/file-uploader.js
Normal file
40
client/src/core-components/file-uploader.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Button from 'core-components/button';
|
||||||
|
import Icon from 'core-components/icon';
|
||||||
|
|
||||||
|
class FileUploader extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
text: React.PropTypes.string,
|
||||||
|
value: React.PropTypes.object,
|
||||||
|
onChange: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
text: 'Upload file'
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<label className="file-uploader">
|
||||||
|
<input className="file-uploader__input" type="file" multiple={false} onChange={this.onChange.bind(this)}/>
|
||||||
|
<span className="file-uploader__custom" tabIndex="0">
|
||||||
|
<Icon className="file-uploader__icon" name="upload" /> {this.props.text}
|
||||||
|
</span>
|
||||||
|
<span className="file-uploader__value">{this.props.value && this.props.value.name}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(event) {
|
||||||
|
if(this.props.onChange) {
|
||||||
|
this.props.onChange({
|
||||||
|
target: {
|
||||||
|
value: event.target.files[0]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileUploader;
|
31
client/src/core-components/file-uploader.scss
Normal file
31
client/src/core-components/file-uploader.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@import "../scss/vars";
|
||||||
|
|
||||||
|
.file-uploader {
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
width: 0.1px;
|
||||||
|
height: 0.1px;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__custom {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
color: white;
|
||||||
|
background-color: $primary-red;
|
||||||
|
line-height: 35px;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: $dark-grey;
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import Checkbox from 'core-components/checkbox';
|
|||||||
import CheckboxGroup from 'core-components/checkbox-group';
|
import CheckboxGroup from 'core-components/checkbox-group';
|
||||||
import TextEditor from 'core-components/text-editor';
|
import TextEditor from 'core-components/text-editor';
|
||||||
import InfoTooltip from 'core-components/info-tooltip';
|
import InfoTooltip from 'core-components/info-tooltip';
|
||||||
|
import FileUploader from 'core-components/file-uploader';
|
||||||
|
|
||||||
class FormField extends React.Component {
|
class FormField extends React.Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -24,7 +25,7 @@ class FormField extends React.Component {
|
|||||||
error: React.PropTypes.string,
|
error: React.PropTypes.string,
|
||||||
infoMessage: React.PropTypes.node,
|
infoMessage: React.PropTypes.node,
|
||||||
value: React.PropTypes.any,
|
value: React.PropTypes.any,
|
||||||
field: React.PropTypes.oneOf(['input', 'textarea', 'select', 'checkbox', 'checkbox-group']),
|
field: React.PropTypes.oneOf(['input', 'textarea', 'select', 'checkbox', 'checkbox-group', 'file']),
|
||||||
fieldProps: React.PropTypes.object
|
fieldProps: React.PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,7 +85,8 @@ class FormField extends React.Component {
|
|||||||
'textarea': TextEditor,
|
'textarea': TextEditor,
|
||||||
'select': DropDown,
|
'select': DropDown,
|
||||||
'checkbox': Checkbox,
|
'checkbox': Checkbox,
|
||||||
'checkbox-group': CheckboxGroup
|
'checkbox-group': CheckboxGroup,
|
||||||
|
'file': FileUploader
|
||||||
}[this.props.field];
|
}[this.props.field];
|
||||||
|
|
||||||
if(this.props.decorator) {
|
if(this.props.decorator) {
|
||||||
@ -142,7 +144,8 @@ class FormField extends React.Component {
|
|||||||
getDivTypes() {
|
getDivTypes() {
|
||||||
return [
|
return [
|
||||||
'textarea',
|
'textarea',
|
||||||
'checkbox-group'
|
'checkbox-group',
|
||||||
|
'file'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
63
client/src/core-components/toggle-list.js
Normal file
63
client/src/core-components/toggle-list.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
class ToggleList extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
items: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||||
|
content: React.PropTypes.node
|
||||||
|
})),
|
||||||
|
onChange: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
selected: []
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="toggle-list">
|
||||||
|
{this.props.items.map(this.renderItem.bind(this))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem(obj, index) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={this.getItemClass(index)} onClick={this.selectItem.bind(this, index)} key={index}>
|
||||||
|
{obj.content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemClass(index) {
|
||||||
|
let classes = {
|
||||||
|
'toggle-list__item': true,
|
||||||
|
'toggle-list__first-item': (index === 0),
|
||||||
|
'toggle-list__selected': _.includes(this.state.selected, index)
|
||||||
|
};
|
||||||
|
|
||||||
|
return classNames(classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectItem(index) {
|
||||||
|
let newSelected = _.clone(this.state.selected);
|
||||||
|
|
||||||
|
_.includes(this.state.selected, index) ? _.remove(newSelected, _index => _index == index) : newSelected.push(index);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selected: newSelected
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange({
|
||||||
|
target: {
|
||||||
|
value: newSelected
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToggleList;
|
21
client/src/core-components/toggle-list.scss
Normal file
21
client/src/core-components/toggle-list.scss
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
@import "../scss/vars";
|
||||||
|
|
||||||
|
.toggle-list {
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
border: 1px $light-grey solid;
|
||||||
|
border-left: none;
|
||||||
|
width: 180px;
|
||||||
|
height: 120px;
|
||||||
|
display: inline-block;
|
||||||
|
transition: background-color 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__selected {
|
||||||
|
background-color: $light-grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__first-item {
|
||||||
|
border: 1px $light-grey solid;
|
||||||
|
}
|
||||||
|
}
|
@ -110,6 +110,14 @@ module.exports = [
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/system/download',
|
||||||
|
time: 100,
|
||||||
|
contentType: 'application/octet-stream',
|
||||||
|
response: function () {
|
||||||
|
return 'text content';
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/system/get-mail-templates',
|
path: '/system/get-mail-templates',
|
||||||
time: 100,
|
time: 100,
|
||||||
|
@ -166,7 +166,7 @@ module.exports = [
|
|||||||
name: 'Sales Support'
|
name: 'Sales Support'
|
||||||
},
|
},
|
||||||
date: '201504090001',
|
date: '201504090001',
|
||||||
file: 'http://www.opensupports.com/some_file.zip',
|
file: 'some_file.txt',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
unread: false,
|
unread: false,
|
||||||
closed: false,
|
closed: false,
|
||||||
@ -385,7 +385,7 @@ module.exports = [
|
|||||||
name: 'Technical Issues'
|
name: 'Technical Issues'
|
||||||
},
|
},
|
||||||
date: '201604161427',
|
date: '201604161427',
|
||||||
file: 'http://www.opensupports.com/some_file.zip',
|
file: 'some_file.txt',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
unread: true,
|
unread: true,
|
||||||
closed: false,
|
closed: false,
|
||||||
@ -504,7 +504,7 @@ module.exports = [
|
|||||||
name: 'Sales Support'
|
name: 'Sales Support'
|
||||||
},
|
},
|
||||||
date: '201604150849',
|
date: '201604150849',
|
||||||
file: 'http://www.opensupports.com/some_file.zip',
|
file: 'some_file.txt',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
unread: false,
|
unread: false,
|
||||||
closed: false,
|
closed: false,
|
||||||
@ -607,7 +607,7 @@ module.exports = [
|
|||||||
name: 'Sales Support'
|
name: 'Sales Support'
|
||||||
},
|
},
|
||||||
date: '201504091032',
|
date: '201504091032',
|
||||||
file: 'http://www.opensupports.com/some_file.zip',
|
file: 'some_file.txt',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
unread: false,
|
unread: false,
|
||||||
closed: false,
|
closed: false,
|
||||||
|
@ -12,13 +12,13 @@ function processData (data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
call: function ({path, data}) {
|
call: function ({path, data, plain}) {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
APIUtils.post(root + path, processData(data))
|
APIUtils.post(root + path, processData(data))
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
console.log(result);
|
console.log(result);
|
||||||
|
|
||||||
if (result.status === 'success') {
|
if (plain || result.status === 'success') {
|
||||||
resolve(result);
|
resolve(result);
|
||||||
} else if (reject) {
|
} else if (reject) {
|
||||||
reject(result);
|
reject(result);
|
||||||
|
@ -24,7 +24,7 @@ fixtures.add(require('data/fixtures/article-fixtures'));
|
|||||||
|
|
||||||
_.each(fixtures.getAll(), function (fixture) {
|
_.each(fixtures.getAll(), function (fixture) {
|
||||||
mockjax({
|
mockjax({
|
||||||
contentType: 'application/json',
|
contentType: fixture.contentType || 'application/json',
|
||||||
url: 'http://localhost:3000/api' + fixture.path,
|
url: 'http://localhost:3000/api' + fixture.path,
|
||||||
responseTime: fixture.time || 500,
|
responseTime: fixture.time || 500,
|
||||||
response: function (settings) {
|
response: function (settings) {
|
||||||
|
@ -9,7 +9,8 @@ const APIUtils = {
|
|||||||
url: path,
|
url: path,
|
||||||
method: method,
|
method: method,
|
||||||
data: data,
|
data: data,
|
||||||
dataType: 'json'
|
processData: false,
|
||||||
|
contentType: false
|
||||||
})
|
})
|
||||||
.done(resolve)
|
.done(resolve)
|
||||||
.fail((jqXHR, textStatus) => {
|
.fail((jqXHR, textStatus) => {
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
"respect/validation": "^1.1",
|
"respect/validation": "^1.1",
|
||||||
"phpmailer/phpmailer": "^5.2",
|
"phpmailer/phpmailer": "^5.2",
|
||||||
"google/recaptcha": "~1.1",
|
"google/recaptcha": "~1.1",
|
||||||
"gabordemooij/redbean": "^4.3"
|
"gabordemooij/redbean": "^4.3",
|
||||||
|
"ifsnop/mysqldump-php": "2.*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "5.0.*"
|
"phpunit/phpunit": "5.0.*"
|
||||||
|
@ -13,6 +13,12 @@ require_once 'system/disable-registration.php';
|
|||||||
require_once 'system/enable-registration.php';
|
require_once 'system/enable-registration.php';
|
||||||
require_once 'system/disable-user-system.php';
|
require_once 'system/disable-user-system.php';
|
||||||
require_once 'system/enabled-user-system.php';
|
require_once 'system/enabled-user-system.php';
|
||||||
|
require_once 'system/add-api-key.php';
|
||||||
|
require_once 'system/delete-api-key.php';
|
||||||
|
require_once 'system/get-all-keys.php';
|
||||||
|
require_once 'system/delete-all-users.php';
|
||||||
|
require_once 'system/backup-database.php';
|
||||||
|
require_once 'system/download.php';
|
||||||
|
|
||||||
$systemControllerGroup = new ControllerGroup();
|
$systemControllerGroup = new ControllerGroup();
|
||||||
$systemControllerGroup->setGroupPath('/system');
|
$systemControllerGroup->setGroupPath('/system');
|
||||||
@ -29,6 +35,13 @@ $systemControllerGroup->addController(new EditMailTemplateController);
|
|||||||
$systemControllerGroup->addController(new RecoverMailTemplateController);
|
$systemControllerGroup->addController(new RecoverMailTemplateController);
|
||||||
$systemControllerGroup->addController(new DisableRegistrationController);
|
$systemControllerGroup->addController(new DisableRegistrationController);
|
||||||
$systemControllerGroup->addController(new EnableRegistrationController);
|
$systemControllerGroup->addController(new EnableRegistrationController);
|
||||||
|
$systemControllerGroup->addController(new GetStatsController);
|
||||||
|
$systemControllerGroup->addController(new AddAPIKeyController);
|
||||||
|
$systemControllerGroup->addController(new DeleteAPIKeyController);
|
||||||
|
$systemControllerGroup->addController(new GetAllKeyController);
|
||||||
|
$systemControllerGroup->addController(new DeleteAllUsersController);
|
||||||
|
$systemControllerGroup->addController(new BackupDatabaseController);
|
||||||
|
$systemControllerGroup->addController(new DownloadController);
|
||||||
$systemControllerGroup->addController(new DisableUserSystemController);
|
$systemControllerGroup->addController(new DisableUserSystemController);
|
||||||
$systemControllerGroup->addController(new EnabledUserSystemController);
|
$systemControllerGroup->addController(new EnabledUserSystemController);
|
||||||
|
|
||||||
|
41
server/controllers/system/add-api-key.php
Normal file
41
server/controllers/system/add-api-key.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
class AddAPIKeyController extends Controller {
|
||||||
|
const PATH = '/add-api-key';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_3',
|
||||||
|
'requestData' => [
|
||||||
|
'name' => [
|
||||||
|
'validation' => DataValidator::length(2, 55)->alnum(),
|
||||||
|
'error' => ERRORS::INVALID_NAME
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$apiInstance = new APIKey();
|
||||||
|
|
||||||
|
$name = Controller::request('name');
|
||||||
|
|
||||||
|
$keyInstance = APIKey::getDataStore($name, 'name');
|
||||||
|
|
||||||
|
if($keyInstance->isNull()){
|
||||||
|
$token = Hashing::generateRandomToken();
|
||||||
|
|
||||||
|
$apiInstance->setProperties([
|
||||||
|
'name' => $name,
|
||||||
|
'token' => $token
|
||||||
|
]);
|
||||||
|
|
||||||
|
$apiInstance->store();
|
||||||
|
Response::respondSuccess($token);
|
||||||
|
} else {
|
||||||
|
Response::respondError(ERRORS::NAME_ALREADY_USED);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
30
server/controllers/system/backup-database.php
Normal file
30
server/controllers/system/backup-database.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
use Ifsnop\Mysqldump as IMysqldump;
|
||||||
|
|
||||||
|
class BackupDatabaseController extends Controller {
|
||||||
|
const PATH = '/backup-database';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_3',
|
||||||
|
'requestData' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
global $mysql_host;
|
||||||
|
global $mysql_database;
|
||||||
|
global $mysql_user;
|
||||||
|
global $mysql_password;
|
||||||
|
|
||||||
|
$fileDownloader = FileDownloader::getInstance();
|
||||||
|
$fileDownloader->setFileName('backup.sql');
|
||||||
|
|
||||||
|
$mysqlDump = new IMysqldump\Mysqldump('mysql:host='. $mysql_host .';dbname=' . $mysql_database, $mysql_user, $mysql_password);
|
||||||
|
$mysqlDump->start($fileDownloader->getFullFilePath());
|
||||||
|
|
||||||
|
if($fileDownloader->download()) {
|
||||||
|
$fileDownloader->eraseFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
server/controllers/system/delete-all-users.php
Normal file
32
server/controllers/system/delete-all-users.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
use RedBeanPHP\Facade as RedBean;
|
||||||
|
|
||||||
|
class DeleteAllUsersController extends Controller {
|
||||||
|
const PATH = '/delete-all-users';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_3',
|
||||||
|
'requestData' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$password = Controller::request('password');
|
||||||
|
|
||||||
|
if(!Hashing::verifyPassword($password, Controller::getLoggedUser()->password)) {
|
||||||
|
Response::respondError(ERRORS::INVALID_PASSWORD);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Redbean::exec('SET FOREIGN_KEY_CHECKS = 0;');
|
||||||
|
RedBean::wipe(SessionCookie::TABLE);
|
||||||
|
RedBean::wipe(User::TABLE);
|
||||||
|
RedBean::wipe(Ticket::TABLE);
|
||||||
|
RedBean::wipe(Ticketevent::TABLE);
|
||||||
|
RedBean::wipe('ticket_user');
|
||||||
|
Redbean::exec('SET FOREIGN_KEY_CHECKS = 1;');
|
||||||
|
|
||||||
|
Response::respondSuccess();
|
||||||
|
}
|
||||||
|
}
|
32
server/controllers/system/delete-api-key.php
Normal file
32
server/controllers/system/delete-api-key.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
class DeleteAPIKeyController extends Controller {
|
||||||
|
const PATH = '/delete-api-key';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_3',
|
||||||
|
'requestData' => [
|
||||||
|
'name' => [
|
||||||
|
'validation' => DataValidator::length(2, 55)->alpha(),
|
||||||
|
'error' => ERRORS::INVALID_NAME
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$name = Controller::request('name');
|
||||||
|
|
||||||
|
$keyInstance = APIKey::getDataStore($name, 'name');
|
||||||
|
|
||||||
|
if($keyInstance->isNull()) {
|
||||||
|
Response::respondError(ERRORS::INVALID_NAME);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyInstance->delete();
|
||||||
|
Response::respondSuccess();
|
||||||
|
}
|
||||||
|
}
|
54
server/controllers/system/download.php
Normal file
54
server/controllers/system/download.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
use Ifsnop\Mysqldump as IMysqldump;
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
class DownloadController extends Controller {
|
||||||
|
const PATH = '/download';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'user',
|
||||||
|
'requestData' => [
|
||||||
|
'file' => [
|
||||||
|
'validation' => DataValidator::alnum('_.-')->noWhitespace(),
|
||||||
|
'error' => ERRORS::INVALID_FILE
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$fileName = Controller::request('file');
|
||||||
|
|
||||||
|
$loggedUser = Controller::getLoggedUser();
|
||||||
|
$ticket = Ticket::getTicket($fileName, 'file');
|
||||||
|
|
||||||
|
if($ticket->isNull() || ($this->isNotAuthor($ticket, $loggedUser) && $this->isNotOwner($ticket, $loggedUser))) {
|
||||||
|
$ticketEvent = Ticketevent::getDataStore($fileName, 'file');
|
||||||
|
|
||||||
|
if($ticketEvent->isNull()) {
|
||||||
|
print '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ticket = $ticketEvent->ticket;
|
||||||
|
|
||||||
|
if($this->isNotAuthor($ticket, $loggedUser) && $this->isNotOwner($ticket, $loggedUser)) {
|
||||||
|
print '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileDownloader = FileDownloader::getInstance();
|
||||||
|
$fileDownloader->setFileName($fileName);
|
||||||
|
$fileDownloader->download();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isNotAuthor($ticket, $loggedUser) {
|
||||||
|
return Controller::isStaffLogged() || $ticket->author->id !== $loggedUser->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isNotOwner($ticket, $loggedUser) {
|
||||||
|
return !Controller::isStaffLogged() || !$ticket->owner || $ticket->owner->id !== $loggedUser->id;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
use Respect\Validation\Validator as DataValidator;
|
|
||||||
|
|
||||||
class EditSettingsController extends Controller {
|
class EditSettingsController extends Controller {
|
||||||
const PATH = '/edit-settings';
|
const PATH = '/edit-settings';
|
||||||
|
19
server/controllers/system/get-all-keys.php
Normal file
19
server/controllers/system/get-all-keys.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
class GetAllKeyController extends Controller {
|
||||||
|
const PATH = '/get-all-keys';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_3',
|
||||||
|
'requestData' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$apiList = APIKey::getAll();
|
||||||
|
|
||||||
|
Response::respondSuccess($apiList->toArray());
|
||||||
|
}
|
||||||
|
}
|
141
server/controllers/system/get-stats.php
Normal file
141
server/controllers/system/get-stats.php
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
class GetStatsController extends Controller {
|
||||||
|
const PATH = '/get-stats';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_1',
|
||||||
|
'requestData' => [
|
||||||
|
'period' => [
|
||||||
|
'validation' => DataValidator::in(['week', 'month', 'quarter', 'year']),
|
||||||
|
'error' => ERRORS::INVALID_PERIOD
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$this->generationNewStats();
|
||||||
|
|
||||||
|
$staffId = Controller::request('staffId');
|
||||||
|
|
||||||
|
if($staffId) {
|
||||||
|
if($staffId !== Controller::getLoggedUser()->id && !Controller::isStaffLogged(3)) {
|
||||||
|
Response::respondError(ERRORS::NO_PERMISSION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getStaffStat();
|
||||||
|
} else {
|
||||||
|
$this->getGeneralStat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generationNewStats() {
|
||||||
|
$lastStatDay = Setting::getSetting('last-stat-day');
|
||||||
|
$previousCurrentDate = floor(Date::getPreviousDate() / 10000);
|
||||||
|
$currentDate = floor(Date::getCurrentDate() / 10000);
|
||||||
|
|
||||||
|
if($lastStatDay->value !== $previousCurrentDate) {
|
||||||
|
|
||||||
|
$begin = new DateTime($lastStatDay->value);
|
||||||
|
$end = new DateTime($currentDate);
|
||||||
|
|
||||||
|
$interval = new DateInterval('P1D');
|
||||||
|
$dateRange = new DatePeriod($begin, $interval ,$end);
|
||||||
|
|
||||||
|
$staffList = Staff::getAll();
|
||||||
|
|
||||||
|
foreach($dateRange as $date) {
|
||||||
|
$this->generateGeneralStat('CREATE_TICKET', $date);
|
||||||
|
$this->generateGeneralStat('CLOSE', $date);
|
||||||
|
$this->generateGeneralStat('SIGNUP', $date);
|
||||||
|
$this->generateGeneralStat('COMMENT', $date);
|
||||||
|
|
||||||
|
foreach($staffList as $staff) {
|
||||||
|
$assignments = Ticketevent::count('type=? AND author_staff_id=? AND date LIKE ?',['ASSIGN',$staff->id, $date->format('Ymd') . '%']);
|
||||||
|
$closed = Ticketevent::count('type=? AND author_staff_id=? AND date LIKE ?',['CLOSE',$staff->id, $date->format('Ymd') . '%']);
|
||||||
|
|
||||||
|
$statAssign = new Stat();
|
||||||
|
$statAssign->setProperties([
|
||||||
|
'date' => $date->format('Ymd'),
|
||||||
|
'type' => 'ASSIGN',
|
||||||
|
'general' => 0,
|
||||||
|
'value' => $assignments,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$statClose = new Stat();
|
||||||
|
$statClose->setProperties([
|
||||||
|
'date' => $date->format('Ymd'),
|
||||||
|
'type' => 'CLOSE',
|
||||||
|
'general' => 0,
|
||||||
|
'value' => $closed,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$staff->ownStatList->add($statAssign);
|
||||||
|
$staff->ownStatList->add($statClose);
|
||||||
|
|
||||||
|
$staff->store();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastStatDay->value = $currentDate;
|
||||||
|
$lastStatDay->store();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateGeneralStat($type, $date) {
|
||||||
|
$value = Log::count('type=? AND date LIKE ?',[$type, $date->format('Ymd') . '%']);
|
||||||
|
$stat = new Stat();
|
||||||
|
|
||||||
|
$stat->setProperties([
|
||||||
|
'date' => $date->format('Ymd'),
|
||||||
|
'type' => $type,
|
||||||
|
'general' => 1,
|
||||||
|
'value' => $value,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$stat->store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGeneralStat() {
|
||||||
|
$daysToRetrieve = $this->getDaysToRetrieve();
|
||||||
|
|
||||||
|
$statList = Stat::find('general=\'1\' ORDER BY id desc LIMIT ? ', [4 * $daysToRetrieve]);
|
||||||
|
|
||||||
|
Response::respondSuccess($statList->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStaffStat() {
|
||||||
|
$staffId = Controller::request('staffId');
|
||||||
|
$daysToRetrieve = $this->getDaysToRetrieve();
|
||||||
|
|
||||||
|
$statList = Stat::find('general=\'0\' AND staff_id=? ORDER BY id desc LIMIT ? ', [$staffId, 4 * $daysToRetrieve]);
|
||||||
|
|
||||||
|
Response::respondSuccess($statList->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDaysToRetrieve() {
|
||||||
|
$period = Controller::request('period');
|
||||||
|
$daysToRetrieve = 0;
|
||||||
|
|
||||||
|
switch ($period) {
|
||||||
|
case 'week':
|
||||||
|
$daysToRetrieve = 7;
|
||||||
|
break;
|
||||||
|
case 'month':
|
||||||
|
$daysToRetrieve = 30;
|
||||||
|
break;
|
||||||
|
case 'quarter':
|
||||||
|
$daysToRetrieve = 90;
|
||||||
|
break;
|
||||||
|
case 'year':
|
||||||
|
$daysToRetrieve = 365;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $daysToRetrieve;
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,12 @@ class InitSettingsController extends Controller {
|
|||||||
'url' => 'http://www.opensupports.com/support',
|
'url' => 'http://www.opensupports.com/support',
|
||||||
'registration' => true,
|
'registration' => true,
|
||||||
'user-system-enabled' => true
|
'user-system-enabled' => true
|
||||||
|
'registration' => true,
|
||||||
|
'last-stat-day' => '20170101', //TODO: get current date
|
||||||
|
'ticket-gap' => Hashing::generateRandomPrime(100000, 999999),
|
||||||
|
'file-gap' => Hashing::generateRandomPrime(100000, 999999),
|
||||||
|
'file-first-number' => Hashing::generateRandomNumber(100000, 999999),
|
||||||
|
'file-quantity' => 0
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ class CommentController extends Controller {
|
|||||||
$comment = Ticketevent::getEvent(Ticketevent::COMMENT);
|
$comment = Ticketevent::getEvent(Ticketevent::COMMENT);
|
||||||
$comment->setProperties(array(
|
$comment->setProperties(array(
|
||||||
'content' => $this->content,
|
'content' => $this->content,
|
||||||
|
'file' => $this->uploadFile(),
|
||||||
'date' => Date::getCurrentDate()
|
'date' => Date::getCurrentDate()
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ class CreateController extends Controller {
|
|||||||
'language' => $this->language,
|
'language' => $this->language,
|
||||||
'author' => $author,
|
'author' => $author,
|
||||||
'department' => $department,
|
'department' => $department,
|
||||||
'file' => '',
|
'file' => $this->uploadFile(),
|
||||||
'date' => Date::getCurrentDate(),
|
'date' => Date::getCurrentDate(),
|
||||||
'unread' => false,
|
'unread' => false,
|
||||||
'unreadStaff' => true,
|
'unreadStaff' => true,
|
||||||
|
@ -41,6 +41,7 @@ class SignUpController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->storeRequestData();
|
$this->storeRequestData();
|
||||||
|
$apiKey = APIKey::getDataStore(Controller::request('apiKey'), 'token');
|
||||||
|
|
||||||
$existentUser = User::getUser($this->userEmail, 'email');
|
$existentUser = User::getUser($this->userEmail, 'email');
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ class SignUpController extends Controller {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Setting::getSetting('registration')->value) {
|
if (!Setting::getSetting('registration')->value && $apiKey->isNull() ) {
|
||||||
Response::respondError(ERRORS::NO_PERMISSION);
|
Response::respondError(ERRORS::NO_PERMISSION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -38,4 +38,7 @@ class ERRORS {
|
|||||||
const USER_SYSTEM_DISABLED= 'USER_SYSTEM_DISABLED';
|
const USER_SYSTEM_DISABLED= 'USER_SYSTEM_DISABLED';
|
||||||
const SYSTEM_USER_IS_ALREADY_DISABLED= 'SYSTEM_USER_IS_ALREADY_DISABLED';
|
const SYSTEM_USER_IS_ALREADY_DISABLED= 'SYSTEM_USER_IS_ALREADY_DISABLED';
|
||||||
const SYSTEM_USER_IS_ALREADY_ENABLED= 'SYSTEM_USER_IS_ALREADY_ENABLED';
|
const SYSTEM_USER_IS_ALREADY_ENABLED= 'SYSTEM_USER_IS_ALREADY_ENABLED';
|
||||||
|
const INVALID_PERIOD = 'INVALID_PERIOD';
|
||||||
|
const NAME_ALREADY_USED = 'NAME_ALREADY_USED';
|
||||||
|
const INVALID_FILE = 'INVALID_FILE';
|
||||||
}
|
}
|
||||||
|
0
server/files/.gitkeep
Normal file
0
server/files/.gitkeep
Normal file
@ -18,6 +18,10 @@ include_once 'libs/Hashing.php';
|
|||||||
include_once 'libs/MailSender.php';
|
include_once 'libs/MailSender.php';
|
||||||
include_once 'libs/Date.php';
|
include_once 'libs/Date.php';
|
||||||
include_once 'libs/DataStoreList.php';
|
include_once 'libs/DataStoreList.php';
|
||||||
|
include_once 'libs/LinearCongruentialGenerator.php';
|
||||||
|
include_once 'libs/FileManager.php';
|
||||||
|
include_once 'libs/FileDownloader.php';
|
||||||
|
include_once 'libs/FileUploader.php';
|
||||||
|
|
||||||
// LOAD DATA
|
// LOAD DATA
|
||||||
spl_autoload_register(function ($class) {
|
spl_autoload_register(function ($class) {
|
||||||
|
@ -61,6 +61,28 @@ abstract class Controller {
|
|||||||
return \Slim\Slim::getInstance();
|
return \Slim\Slim::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function uploadFile() {
|
||||||
|
if(!isset($_FILES['file'])) return '';
|
||||||
|
|
||||||
|
$maxSize = Setting::getSetting('max-size')->getValue();
|
||||||
|
$fileGap = Setting::getSetting('file-gap')->getValue();
|
||||||
|
$fileFirst = Setting::getSetting('file-first-number')->getValue();
|
||||||
|
$fileQuantity = Setting::getSetting('file-quantity');
|
||||||
|
|
||||||
|
$fileUploader = FileUploader::getInstance();
|
||||||
|
$fileUploader->setMaxSize($maxSize);
|
||||||
|
$fileUploader->setGeneratorValues($fileGap, $fileFirst, $fileQuantity->getValue());
|
||||||
|
|
||||||
|
if($fileUploader->upload($_FILES['file'])) {
|
||||||
|
$fileQuantity->value++;
|
||||||
|
$fileQuantity->store();
|
||||||
|
|
||||||
|
return $fileUploader->getFileName();
|
||||||
|
} else {
|
||||||
|
throw new Exception(ERRORS::INVALID_FILE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function isUserSystemEnabled() {
|
public static function isUserSystemEnabled() {
|
||||||
return Setting::getSetting('user-system-enabled')->getValue();
|
return Setting::getSetting('user-system-enabled')->getValue();
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,8 @@ class Date {
|
|||||||
public static function getCurrentDate() {
|
public static function getCurrentDate() {
|
||||||
return date('YmdHi');
|
return date('YmdHi');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getPreviousDate() {
|
||||||
|
return date('YmdHi', strtotime(' -1 day '));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
40
server/libs/FileDownloader.php
Normal file
40
server/libs/FileDownloader.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class FileDownloader extends FileManager {
|
||||||
|
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
|
public static function getInstance() {
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new FileDownloader();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function __construct() {}
|
||||||
|
|
||||||
|
public function download() {
|
||||||
|
$fullFilePath = $this->getFullFilePath();
|
||||||
|
|
||||||
|
if(file_exists($fullFilePath) && is_file($fullFilePath)) {
|
||||||
|
header('Cache-control: private');
|
||||||
|
header('Content-Type: application/octet-stream');
|
||||||
|
header('Content-Length: '.filesize($fullFilePath));
|
||||||
|
header('Content-Disposition: filename='. $this->getFileName());
|
||||||
|
|
||||||
|
flush();
|
||||||
|
$file = fopen($fullFilePath, 'r');
|
||||||
|
print fread($file, filesize($fullFilePath));
|
||||||
|
fclose($file);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eraseFile() {
|
||||||
|
unlink($this->getLocalPath() . $this->getFileName());
|
||||||
|
}
|
||||||
|
}
|
26
server/libs/FileManager.php
Normal file
26
server/libs/FileManager.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class FileManager {
|
||||||
|
private $fileName;
|
||||||
|
private $localPath = 'files/';
|
||||||
|
|
||||||
|
public function setLocalPath($localPath) {
|
||||||
|
$this->localPath = $localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFileName($fileName) {
|
||||||
|
$this->fileName = $fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLocalPath() {
|
||||||
|
return $this->localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileName() {
|
||||||
|
return $this->fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFullFilePath() {
|
||||||
|
return $this->getLocalPath() . $this->getFileName();
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
class FileUploader {
|
|
||||||
|
class FileUploader extends FileManager {
|
||||||
|
private $maxSize = 1024;
|
||||||
|
private $linearCongruentialGenerator;
|
||||||
|
private $linearCongruentialGeneratorOffset;
|
||||||
|
private $fileName;
|
||||||
|
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
|
||||||
public static function getInstance() {
|
public static function getInstance() {
|
||||||
@ -12,7 +18,44 @@ class FileUploader {
|
|||||||
|
|
||||||
private function __construct() {}
|
private function __construct() {}
|
||||||
|
|
||||||
public function upload() {
|
public function upload($file) {
|
||||||
// TODO: Implement file upload features
|
$this->setNewName($file['name']);
|
||||||
|
|
||||||
|
if($file['size'] > (1024 * $this->maxSize)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
move_uploaded_file($file['tmp_name'], $this->getLocalPath() . $this->getFileName());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setNewName($fileName) {
|
||||||
|
$newName = $fileName;
|
||||||
|
$newName = strtolower($newName);
|
||||||
|
$newName = preg_replace('/\s+/', '_', $newName);
|
||||||
|
|
||||||
|
if ($this->linearCongruentialGenerator instanceof LinearCongruentialGenerator) {
|
||||||
|
$newName = $this->linearCongruentialGenerator->generate($this->linearCongruentialGeneratorOffset) . '_' . $newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fileName = $newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setGeneratorValues($gap, $first, $offset) {
|
||||||
|
$this->linearCongruentialGenerator = new LinearCongruentialGenerator();
|
||||||
|
$this->linearCongruentialGeneratorOffset = $offset;
|
||||||
|
|
||||||
|
$this->linearCongruentialGenerator->setGap($gap);
|
||||||
|
$this->linearCongruentialGenerator->setFirst($first);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMaxSize($maxSize) {
|
||||||
|
$this->maxSize = $maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileName() {
|
||||||
|
return $this->fileName;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -7,10 +7,36 @@ class Hashing {
|
|||||||
public static function verifyPassword($password, $hash) {
|
public static function verifyPassword($password, $hash) {
|
||||||
return password_verify($password, $hash);
|
return password_verify($password, $hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function generateRandomToken() {
|
public static function generateRandomToken() {
|
||||||
return md5(uniqid(rand()));
|
return md5(uniqid(rand()));
|
||||||
}
|
}
|
||||||
public static function getRandomTicketNumber($min,$max) {
|
|
||||||
|
public static function generateRandomNumber($min, $max) {
|
||||||
return rand($min, $max);
|
return rand($min, $max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function generateRandomPrime($min, $max) {
|
||||||
|
$number = Hashing::generateRandomNumber($min, $max);
|
||||||
|
|
||||||
|
while(!Hashing::isPrime($number)) {
|
||||||
|
$number = Hashing::generateRandomNumber($min, $max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isPrime($number) {
|
||||||
|
$sqrt = sqrt($number);
|
||||||
|
$prime = true;
|
||||||
|
|
||||||
|
for($i = 0; $i < $sqrt; $i++) {
|
||||||
|
if($sqrt % 2 === 0) {
|
||||||
|
$prime = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $prime;
|
||||||
|
}
|
||||||
}
|
}
|
31
server/libs/LinearCongruentialGenerator.php
Normal file
31
server/libs/LinearCongruentialGenerator.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
class LinearCongruentialGenerator {
|
||||||
|
private $gap;
|
||||||
|
private $first;
|
||||||
|
private $min = 100000;
|
||||||
|
private $max = 999999;
|
||||||
|
|
||||||
|
public function setRange($min, $max) {
|
||||||
|
$this->min = $min;
|
||||||
|
$this->max = $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setGap($gap) {
|
||||||
|
if(!Hashing::isPrime($gap)) throw new Exception('LinearCongruentialGenerator: gap must be prime');
|
||||||
|
|
||||||
|
$this->gap = $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFirst($first) {
|
||||||
|
$this->first = $first;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate($offset) {
|
||||||
|
if($offset) return ($this->first - $this->min + $offset * $this->gap) % ($this->max - $this->min + 1) + $this->min;
|
||||||
|
else return $this->generateFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateFirst() {
|
||||||
|
return Hashing::generateRandomNumber($this->min, $this->max);
|
||||||
|
}
|
||||||
|
}
|
@ -8,8 +8,9 @@ class Captcha extends AbstractRule {
|
|||||||
|
|
||||||
public function validate($reCaptchaResponse) {
|
public function validate($reCaptchaResponse) {
|
||||||
$reCaptchaPrivateKey = \Setting::getSetting('recaptcha-private')->getValue();
|
$reCaptchaPrivateKey = \Setting::getSetting('recaptcha-private')->getValue();
|
||||||
|
$apiKey = \APIKey::getDataStore(\Controller::request('apiKey'), 'token');
|
||||||
|
|
||||||
if (!$reCaptchaPrivateKey) return true;
|
if (!$reCaptchaPrivateKey || !$apiKey->isNull()) return true;
|
||||||
|
|
||||||
$reCaptcha = new \ReCaptcha\ReCaptcha($reCaptchaPrivateKey);
|
$reCaptcha = new \ReCaptcha\ReCaptcha($reCaptchaPrivateKey);
|
||||||
$reCaptchaValidation = $reCaptcha->verify($reCaptchaResponse, $_SERVER['REMOTE_ADDR']);
|
$reCaptchaValidation = $reCaptcha->verify($reCaptchaResponse, $_SERVER['REMOTE_ADDR']);
|
||||||
|
19
server/models/APIKey.php
Normal file
19
server/models/APIKey.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class APIKey extends DataStore {
|
||||||
|
const TABLE = 'apikey';
|
||||||
|
|
||||||
|
public static function getProps() {
|
||||||
|
return [
|
||||||
|
'name',
|
||||||
|
'token'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
'name' => $this->name,
|
||||||
|
'token' => $this->token
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,8 @@ class Log extends DataStore {
|
|||||||
'type',
|
'type',
|
||||||
'authorUser',
|
'authorUser',
|
||||||
'authorStaff',
|
'authorStaff',
|
||||||
'to'
|
'to',
|
||||||
|
'date'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +23,8 @@ class Log extends DataStore {
|
|||||||
|
|
||||||
$log->setProperties(array(
|
$log->setProperties(array(
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
'to' => $to
|
'to' => $to,
|
||||||
|
'date' => Date::getCurrentDate()
|
||||||
));
|
));
|
||||||
|
|
||||||
if($author instanceof User) {
|
if($author instanceof User) {
|
||||||
@ -44,7 +46,8 @@ class Log extends DataStore {
|
|||||||
'name' => $author->name,
|
'name' => $author->name,
|
||||||
'id' => $author->id,
|
'id' => $author->id,
|
||||||
'staff' => $author instanceof Staff
|
'staff' => $author instanceof Staff
|
||||||
]
|
],
|
||||||
|
'date' => $this->date
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,13 +18,15 @@ class Staff extends DataStore {
|
|||||||
'level',
|
'level',
|
||||||
'sharedDepartmentList',
|
'sharedDepartmentList',
|
||||||
'sharedTicketList',
|
'sharedTicketList',
|
||||||
'lastLogin'
|
'lastLogin',
|
||||||
|
'ownStatList'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDefaultProps() {
|
public function getDefaultProps() {
|
||||||
return [
|
return [
|
||||||
'level' => 1
|
'level' => 1,
|
||||||
|
'ownStatList' => new DataStoreList()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
server/models/Stat.php
Normal file
25
server/models/Stat.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
class Stat extends DataStore {
|
||||||
|
const TABLE = 'stat';
|
||||||
|
|
||||||
|
public static function getProps() {
|
||||||
|
return array (
|
||||||
|
'date',
|
||||||
|
'type',
|
||||||
|
'general',
|
||||||
|
'value'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefaultProps() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
'date' => $this->date,
|
||||||
|
'type' => $this->type,
|
||||||
|
'general' => $this->general,
|
||||||
|
'value' => $this->value
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -48,17 +48,16 @@ class Ticket extends DataStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function generateUniqueTicketNumber() {
|
public function generateUniqueTicketNumber() {
|
||||||
|
$linearCongruentialGenerator = new LinearCongruentialGenerator();
|
||||||
$ticketQuantity = Ticket::count();
|
$ticketQuantity = Ticket::count();
|
||||||
$minValue = 100000;
|
|
||||||
$maxValue = 999999;
|
|
||||||
|
|
||||||
if ($ticketQuantity === 0) {
|
if ($ticketQuantity === 0) {
|
||||||
$ticketNumber = Hashing::getRandomTicketNumber($minValue, $maxValue);
|
$ticketNumber = $linearCongruentialGenerator->generateFirst();
|
||||||
} else {
|
} else {
|
||||||
$firstTicketNumber = Ticket::getTicket(1)->ticketNumber;
|
$linearCongruentialGenerator->setGap(Setting::getSetting('ticket-gap')->value);
|
||||||
$gap = 176611; //TODO: USE RANDOM PRIME INSTEAD
|
$linearCongruentialGenerator->setFirst(Ticket::getTicket(1)->ticketNumber);
|
||||||
|
|
||||||
$ticketNumber = ($firstTicketNumber - $minValue + $ticketQuantity * $gap) % ($maxValue - $minValue + 1) + $minValue;
|
$ticketNumber = $linearCongruentialGenerator->generate($ticketQuantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ticketNumber;
|
return $ticketNumber;
|
||||||
|
@ -36,7 +36,7 @@ class Ticketevent extends DataStore {
|
|||||||
return $ticketEvent;
|
return $ticketEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProps() {
|
public static function getProps() {
|
||||||
return [
|
return [
|
||||||
'type',
|
'type',
|
||||||
'content',
|
'content',
|
||||||
|
12
server/tests/__mocks__/APIKeyMock.php
Normal file
12
server/tests/__mocks__/APIKeyMock.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
include_once 'tests/__mocks__/NullDataStoreMock.php';
|
||||||
|
|
||||||
|
class APIKey extends \Mock {
|
||||||
|
public static $functionList = array();
|
||||||
|
|
||||||
|
public static function initStubs() {
|
||||||
|
parent::setStatics(array(
|
||||||
|
'getDataStore' => parent::stub()->returns(new NullDataStore()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ class User extends \Mock {
|
|||||||
public static function initStubs() {
|
public static function initStubs() {
|
||||||
parent::setStatics(array(
|
parent::setStatics(array(
|
||||||
'authenticate' => parent::stub()->returns(self::getUserInstanceMock()),
|
'authenticate' => parent::stub()->returns(self::getUserInstanceMock()),
|
||||||
|
'getDataStore' => parent::stub()->returns(self::getUserInstanceMock())
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ class User extends \Mock {
|
|||||||
$mockUserInstance->id = 'MOCK_ID';
|
$mockUserInstance->id = 'MOCK_ID';
|
||||||
$mockUserInstance->email = 'MOCK_EMAIL';
|
$mockUserInstance->email = 'MOCK_EMAIL';
|
||||||
$mockUserInstance->password = 'MOCK_PASSWORD';
|
$mockUserInstance->password = 'MOCK_PASSWORD';
|
||||||
|
$mockUserInstance->verificationToken = null;
|
||||||
|
|
||||||
return $mockUserInstance;
|
return $mockUserInstance;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
include_once 'tests/__lib__/Mock.php';
|
include_once 'tests/__lib__/Mock.php';
|
||||||
include_once 'tests/__mocks__/RespectMock.php';
|
include_once 'tests/__mocks__/RespectMock.php';
|
||||||
include_once 'tests/__mocks__/SettingMock.php';
|
include_once 'tests/__mocks__/SettingMock.php';
|
||||||
|
include_once 'tests/__mocks__/APIKeyMock.php';
|
||||||
|
include_once 'tests/__mocks__/ControllerMock.php';
|
||||||
include_once 'tests/__mocks__/ReCaptchaMock.php';
|
include_once 'tests/__mocks__/ReCaptchaMock.php';
|
||||||
|
|
||||||
include_once 'libs/validations/captcha.php';
|
include_once 'libs/validations/captcha.php';
|
||||||
@ -10,6 +12,8 @@ class CaptchaValidationTest extends PHPUnit_Framework_TestCase {
|
|||||||
|
|
||||||
protected function setUp() {
|
protected function setUp() {
|
||||||
Setting::initStubs();
|
Setting::initStubs();
|
||||||
|
Controller::initStubs();
|
||||||
|
APIKey::initStubs();
|
||||||
\ReCaptcha\ReCaptcha::initVerify();
|
\ReCaptcha\ReCaptcha::initVerify();
|
||||||
|
|
||||||
$_SERVER['REMOTE_ADDR'] = 'MOCK_REMOTE';
|
$_SERVER['REMOTE_ADDR'] = 'MOCK_REMOTE';
|
||||||
|
@ -54,3 +54,8 @@ require './system/recover-mail-template.rb'
|
|||||||
require './system/disable-registration.rb'
|
require './system/disable-registration.rb'
|
||||||
require './system/enable-registration.rb'
|
require './system/enable-registration.rb'
|
||||||
require './system/disable-user-system.rb'
|
require './system/disable-user-system.rb'
|
||||||
|
require './system/get-stats.rb'
|
||||||
|
require './system/add-api-key.rb'
|
||||||
|
require './system/delete-api-key.rb'
|
||||||
|
require './system/get-all-keys.rb'
|
||||||
|
require './system/file-upload-download.rb'
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
$agent = Mechanize.new
|
$agent = Mechanize.new
|
||||||
|
|
||||||
|
def plainRequest(path, data = {})
|
||||||
|
uri = 'http://localhost:8080' + path
|
||||||
|
response = $agent.post(uri, data)
|
||||||
|
|
||||||
|
return response
|
||||||
|
end
|
||||||
|
|
||||||
def request(path, data = {})
|
def request(path, data = {})
|
||||||
uri = 'http://localhost:8080' + path
|
uri = 'http://localhost:8080' + path
|
||||||
response = $agent.post(uri, data)
|
response = $agent.post(uri, data)
|
||||||
@ -29,6 +36,10 @@ class Database
|
|||||||
|
|
||||||
return queryResponse.fetch_hash
|
return queryResponse.fetch_hash
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def query(query_string)
|
||||||
|
return @connection.query(query_string);
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
$database = Database.new
|
$database = Database.new
|
||||||
|
@ -44,4 +44,12 @@ class Scripts
|
|||||||
|
|
||||||
result['data']
|
result['data']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.createAPIKey(name)
|
||||||
|
result = request('/system/add-api-key', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: name
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
30
tests/system/add-api-key.rb
Normal file
30
tests/system/add-api-key.rb
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
describe'system/add-api-key' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
it 'should add API key' do
|
||||||
|
result= request('/system/add-api-key', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: 'new API'
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
|
||||||
|
row = $database.getRow('apikey', 1, 'id')
|
||||||
|
|
||||||
|
(row['name']).should.equal('new API')
|
||||||
|
(result['data']).should.equal(row['token'])
|
||||||
|
|
||||||
|
end
|
||||||
|
it 'should not add API key' do
|
||||||
|
result= request('/system/add-api-key', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: 'new API'
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('NAME_ALREADY_USED')
|
||||||
|
end
|
||||||
|
end
|
30
tests/system/delete-api-key.rb
Normal file
30
tests/system/delete-api-key.rb
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
describe'system/delete-api-key' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
it 'should not delete API key' do
|
||||||
|
result= request('/system/delete-api-key', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: 'new PIA'
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_NAME')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should delete API key' do
|
||||||
|
result= request('/system/delete-api-key', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: 'new API'
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
|
||||||
|
row = $database.getRow('apikey', 1, 'id')
|
||||||
|
|
||||||
|
(row).should.equal(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -10,7 +10,7 @@ describe'system/edit-settings' do
|
|||||||
"time-zone" => -3,
|
"time-zone" => -3,
|
||||||
"layout" => 'full-width',
|
"layout" => 'full-width',
|
||||||
"allow-attachments" => 1,
|
"allow-attachments" => 1,
|
||||||
"max-size" => 2,
|
"max-size" => 2048,
|
||||||
"language" => 'en',
|
"language" => 'en',
|
||||||
"no-reply-email" => 'testemail@hotmail.com'
|
"no-reply-email" => 'testemail@hotmail.com'
|
||||||
})
|
})
|
||||||
@ -27,7 +27,7 @@ describe'system/edit-settings' do
|
|||||||
(row['value']).should.equal('full-width')
|
(row['value']).should.equal('full-width')
|
||||||
|
|
||||||
row = $database.getRow('setting', 'max-size', 'name')
|
row = $database.getRow('setting', 'max-size', 'name')
|
||||||
(row['value']).should.equal('2')
|
(row['value']).should.equal('2048')
|
||||||
|
|
||||||
row = $database.getRow('setting', 'language', 'name')
|
row = $database.getRow('setting', 'language', 'name')
|
||||||
(row['value']).should.equal('en')
|
(row['value']).should.equal('en')
|
||||||
|
74
tests/system/file-upload-download.rb
Normal file
74
tests/system/file-upload-download.rb
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
describe 'File Upload and Download' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login('creator@os4.com', 'creator')
|
||||||
|
|
||||||
|
it 'should upload file when creating ticket' do
|
||||||
|
file = File.new('../server/files/upload.txt', 'w+')
|
||||||
|
file.puts('file content')
|
||||||
|
file.close
|
||||||
|
|
||||||
|
result = request('/ticket/create', {
|
||||||
|
'csrf_userid' => $csrf_userid,
|
||||||
|
'csrf_token' => $csrf_token,
|
||||||
|
'title' => 'Ticket with file',
|
||||||
|
'content' => 'this is a ticket that contains a file',
|
||||||
|
'language' => 'en',
|
||||||
|
'departmentId' => 1,
|
||||||
|
'file' => File.open( "../server/files/upload.txt")
|
||||||
|
})
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
|
||||||
|
ticket = $database.getLastRow('ticket')
|
||||||
|
|
||||||
|
(ticket['file'].include? 'upload.txt').should.equal(true)
|
||||||
|
(File.exist? ('../server/files/' + ticket['file'])).should.equal(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should download file if author is logged' do
|
||||||
|
ticket = $database.getLastRow('ticket')
|
||||||
|
file = File.open("../server/files/" + ticket['file'])
|
||||||
|
|
||||||
|
result = plainRequest('/system/download', {
|
||||||
|
'csrf_userid' => $csrf_userid,
|
||||||
|
'csrf_token' => $csrf_token,
|
||||||
|
'file' => ticket['file']
|
||||||
|
})
|
||||||
|
|
||||||
|
(result.body).should.equal(file.read)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not download if author is not logged' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login('staff@opensupports.com', 'staff', true)
|
||||||
|
|
||||||
|
ticket = $database.getLastRow('ticket')
|
||||||
|
|
||||||
|
result = plainRequest('/system/download', {
|
||||||
|
'csrf_userid' => $csrf_userid,
|
||||||
|
'csrf_token' => $csrf_token,
|
||||||
|
'file' => ticket['file']
|
||||||
|
})
|
||||||
|
|
||||||
|
(result.body).should.equal('')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should download if owner is logged' do
|
||||||
|
ticket = $database.getLastRow('ticket')
|
||||||
|
file = File.open("../server/files/" + ticket['file'])
|
||||||
|
|
||||||
|
request('/staff/assign-ticket', {
|
||||||
|
'csrf_userid' => $csrf_userid,
|
||||||
|
'csrf_token' => $csrf_token,
|
||||||
|
'ticketNumber' => ticket['ticket_number']
|
||||||
|
})
|
||||||
|
|
||||||
|
result = plainRequest('/system/download', {
|
||||||
|
'csrf_userid' => $csrf_userid,
|
||||||
|
'csrf_token' => $csrf_token,
|
||||||
|
'file' => ticket['file']
|
||||||
|
})
|
||||||
|
|
||||||
|
(result.body).should.equal(file.read)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
26
tests/system/get-all-keys.rb
Normal file
26
tests/system/get-all-keys.rb
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
describe'system/get-all-keys' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
it 'should get all API keys' do
|
||||||
|
Scripts.createAPIKey('namekey1')
|
||||||
|
Scripts.createAPIKey('namekey2')
|
||||||
|
Scripts.createAPIKey('namekey3')
|
||||||
|
Scripts.createAPIKey('namekey4')
|
||||||
|
Scripts.createAPIKey('namekey5')
|
||||||
|
|
||||||
|
result= request('/system/get-all-keys', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
(result['data'][0]['name']).should.equal('namekey1')
|
||||||
|
(result['data'][1]['name']).should.equal('namekey2')
|
||||||
|
(result['data'][2]['name']).should.equal('namekey3')
|
||||||
|
(result['data'][3]['name']).should.equal('namekey4')
|
||||||
|
(result['data'][4]['name']).should.equal('namekey5')
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
160
tests/system/get-stats.rb
Normal file
160
tests/system/get-stats.rb
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
describe'/system/get-stats' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
it 'should get stats' do
|
||||||
|
|
||||||
|
d = Date.today.prev_day
|
||||||
|
yesterday = d.strftime("%Y%m%d%H%M")
|
||||||
|
d = Date.today.prev_day.prev_day
|
||||||
|
yesterday2 = d.strftime("%Y%m%d%H%M")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day
|
||||||
|
yesterday3 = d.strftime("%Y%m%d%H%M")
|
||||||
|
|
||||||
|
#day 1
|
||||||
|
for i in 0..5
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'SIGNUP', NULL, " + yesterday3 + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..0
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'CREATE_TICKET', NULL, " + yesterday3 + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..1
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'CLOSE', NULL, " + yesterday3 + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..2
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'COMMENT', NULL, " + yesterday3 + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..8
|
||||||
|
$database.query("INSERT INTO ticketevent VALUES('', 'CLOSE', NULL, NULL, " + yesterday3 + ", NULL, NULL, 1);")
|
||||||
|
end
|
||||||
|
for i in 0..4
|
||||||
|
$database.query("INSERT INTO ticketevent VALUES('', 'ASSIGN', NULL, NULL, " + yesterday3 + ", NULL, NULL, 1);")
|
||||||
|
end
|
||||||
|
|
||||||
|
#day 2
|
||||||
|
for i in 0..7
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'SIGNUP', NULL, " + yesterday2 + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..2
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'CREATE_TICKET', NULL, " + yesterday2 + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..9
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'CLOSE', NULL, " + yesterday2 + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..2
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'COMMENT', NULL, " + yesterday2 + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..10
|
||||||
|
$database.query("INSERT INTO ticketevent VALUES('', 'CLOSE', NULL, NULL, " + yesterday2 + ", NULL, NULL, 1);")
|
||||||
|
end
|
||||||
|
for i in 0..2
|
||||||
|
$database.query("INSERT INTO ticketevent VALUES('', 'ASSIGN', NULL, NULL, " + yesterday2 + ", NULL, NULL, 1);")
|
||||||
|
end
|
||||||
|
|
||||||
|
#day 3
|
||||||
|
for i in 0..0
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'SIGNUP', NULL, " + yesterday + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..1
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'CREATE_TICKET', NULL, " + yesterday + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..4
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'CLOSE', NULL, " + yesterday + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..7
|
||||||
|
$database.query("INSERT INTO log VALUES('', 'COMMENT', NULL, " + yesterday + ", NULL, NULL);")
|
||||||
|
end
|
||||||
|
for i in 0..3
|
||||||
|
$database.query("INSERT INTO ticketevent VALUES('', 'CLOSE', NULL, NULL, " + yesterday + ", NULL, NULL, 1);")
|
||||||
|
end
|
||||||
|
for i in 0..7
|
||||||
|
$database.query("INSERT INTO ticketevent VALUES('', 'ASSIGN', NULL, NULL, " + yesterday + ", NULL, NULL, 1);")
|
||||||
|
end
|
||||||
|
|
||||||
|
@result = request('/system/get-stats', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
period: 'week'
|
||||||
|
})
|
||||||
|
|
||||||
|
def assertData(position, date, type, value)
|
||||||
|
(@result['data'][position]['date']).should.equal(date)
|
||||||
|
(@result['data'][position]['type']).should.equal(type)
|
||||||
|
(@result['data'][position]['value']).should.equal(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
d = Date.today.prev_day
|
||||||
|
yesterday = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day
|
||||||
|
yesterday2 = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day
|
||||||
|
yesterday3 = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day.prev_day
|
||||||
|
yesterday4 = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day.prev_day.prev_day
|
||||||
|
yesterday5 = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day
|
||||||
|
yesterday6 = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day
|
||||||
|
yesterday7 = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day
|
||||||
|
yesterday8 = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day
|
||||||
|
yesterday9 = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day
|
||||||
|
yesterday10 = d.strftime("%Y%m%d")
|
||||||
|
d = Date.today.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day.prev_day
|
||||||
|
yesterday11 = d.strftime("%Y%m%d")
|
||||||
|
|
||||||
|
assertData(11, yesterday3, 'CREATE_TICKET', '1')
|
||||||
|
assertData(10, yesterday3, 'CLOSE', '2')
|
||||||
|
assertData(9, yesterday3, 'SIGNUP', '6')
|
||||||
|
assertData(8, yesterday3, 'COMMENT', '3')
|
||||||
|
|
||||||
|
|
||||||
|
assertData(7, yesterday2, 'CREATE_TICKET', '3')
|
||||||
|
assertData(6, yesterday2, 'CLOSE', '10')
|
||||||
|
assertData(5, yesterday2, 'SIGNUP', '8')
|
||||||
|
assertData(4, yesterday2, 'COMMENT', '3')
|
||||||
|
|
||||||
|
assertData(3, yesterday, 'CREATE_TICKET', '2')
|
||||||
|
assertData(2, yesterday, 'CLOSE', '5')
|
||||||
|
assertData(1, yesterday, 'SIGNUP', '1')
|
||||||
|
assertData(0, yesterday, 'COMMENT', '8')
|
||||||
|
|
||||||
|
|
||||||
|
@result = request('/system/get-stats', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
period: 'week',
|
||||||
|
staffId: '1'
|
||||||
|
})
|
||||||
|
assertData(0, yesterday, 'CLOSE', '4')
|
||||||
|
assertData(1, yesterday, 'ASSIGN', '8')
|
||||||
|
assertData(2, yesterday2, 'CLOSE', '11')
|
||||||
|
assertData(3, yesterday2, 'ASSIGN', '3')
|
||||||
|
|
||||||
|
assertData(4, yesterday3, 'CLOSE', '9')
|
||||||
|
assertData(5, yesterday3, 'ASSIGN', '5')
|
||||||
|
assertData(6, yesterday4, 'CLOSE', '0')
|
||||||
|
assertData(7, yesterday4, 'ASSIGN', '0')
|
||||||
|
|
||||||
|
assertData(8, yesterday5, 'CLOSE', '0')
|
||||||
|
assertData(9, yesterday5, 'ASSIGN', '0')
|
||||||
|
assertData(10, yesterday6, 'CLOSE', '0')
|
||||||
|
assertData(11, yesterday6, 'ASSIGN', '0')
|
||||||
|
|
||||||
|
assertData(12, yesterday7, 'CLOSE', '0')
|
||||||
|
assertData(13, yesterday7, 'ASSIGN', '0')
|
||||||
|
assertData(14, yesterday8, 'CLOSE', '0')
|
||||||
|
assertData(15, yesterday8, 'ASSIGN', '0')
|
||||||
|
|
||||||
|
assertData(16, yesterday9, 'CLOSE', '0')
|
||||||
|
assertData(17, yesterday9, 'ASSIGN', '0')
|
||||||
|
assertData(18, yesterday10, 'CLOSE', '0')
|
||||||
|
assertData(19, yesterday10, 'ASSIGN', '0')
|
||||||
|
|
||||||
|
assertData(20, yesterday11, 'CLOSE', '0')
|
||||||
|
assertData(21, yesterday11, 'ASSIGN', '0')
|
||||||
|
end
|
||||||
|
end
|
@ -147,13 +147,15 @@ describe '/ticket/create' do
|
|||||||
csrf_token: $csrf_token
|
csrf_token: $csrf_token
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ticket_number_gap = $database.getRow('setting', 'ticket-gap', 'name')['value'].to_i
|
||||||
|
|
||||||
ticket0 = $database.getRow('ticket','Winter is coming','title')['ticket_number'].to_i
|
ticket0 = $database.getRow('ticket','Winter is coming','title')['ticket_number'].to_i
|
||||||
ticket1 = $database.getRow('ticket','Winter is coming1','title')['ticket_number'].to_i
|
ticket1 = $database.getRow('ticket','Winter is coming1','title')['ticket_number'].to_i
|
||||||
ticket2 = $database.getRow('ticket','Winter is coming2','title')['ticket_number'].to_i
|
ticket2 = $database.getRow('ticket','Winter is coming2','title')['ticket_number'].to_i
|
||||||
ticket3 = $database.getRow('ticket','Winter is coming3','title')['ticket_number'].to_i
|
ticket3 = $database.getRow('ticket','Winter is coming3','title')['ticket_number'].to_i
|
||||||
|
|
||||||
(ticket1).should.equal((ticket0 - 100000 + 1 * 176611) % 900000 + 100000)
|
(ticket1).should.equal((ticket0 - 100000 + 1 * ticket_number_gap) % 900000 + 100000)
|
||||||
(ticket2).should.equal((ticket0 - 100000 + 2 * 176611) % 900000 + 100000)
|
(ticket2).should.equal((ticket0 - 100000 + 2 * ticket_number_gap) % 900000 + 100000)
|
||||||
(ticket3).should.equal((ticket0 - 100000 + 3 * 176611) % 900000 + 100000)
|
(ticket3).should.equal((ticket0 - 100000 + 3 * ticket_number_gap) % 900000 + 100000)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user