Ivan - Fix AreYouSure design, fix stats, rename get-api-keys, use allow-attachment, fix login verificationToken, fix deletion, fix ticket view permission, fix last events when empty, fix configuration in frontend, fix system preferences and my account

This commit is contained in:
ivan 2017-02-24 03:56:25 -03:00
parent 3d27415041
commit 5126b40538
36 changed files with 169 additions and 91 deletions

View File

@ -1,9 +1,9 @@
export default { export default {
openModal(content) { openModal(config) {
return { return {
type: 'OPEN_MODAL', type: 'OPEN_MODAL',
payload: content payload: config
} }
}, },

View File

@ -68,7 +68,7 @@ class ActivityRow extends React.Component {
</Link> </Link>
</span> </span>
<span className="activity-row__message"> {i18n('ACTIVITY_' + this.props.type)} </span> <span className="activity-row__message"> {i18n('ACTIVITY_' + this.props.type)} </span>
{_.includes(ticketRelatedTypes, this.props.type) ? this.renderTicketNumber() : null} {_.includes(ticketRelatedTypes, this.props.type) ? this.renderTicketNumber() : this.props.to}
<span className="separator" /> <span className="separator" />
</div> </div>
); );

View File

@ -19,8 +19,8 @@
&__ticket-link { &__ticket-link {
} }
}
.separator { .separator {
margin: 15px; margin: 15px;
} }
}

View File

@ -1,9 +1,12 @@
import React from 'react'; import React from 'react';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
import ModalContainer from 'app-components/modal-container';
import Button from 'core-components/button'; import Button from 'core-components/button';
import Input from 'core-components/input'; import Input from 'core-components/input';
import ModalContainer from 'app-components/modal-container'; import Icon from 'core-components/icon';
class AreYouSure extends React.Component { class AreYouSure extends React.Component {
static propTypes = { static propTypes = {
@ -26,7 +29,8 @@ class AreYouSure extends React.Component {
static openModal(description, onYes, type) { static openModal(description, onYes, type) {
ModalContainer.openModal( ModalContainer.openModal(
<AreYouSure description={description} onYes={onYes} type={type}/> <AreYouSure description={description} onYes={onYes} type={type}/>,
true
); );
} }
@ -40,21 +44,25 @@ class AreYouSure extends React.Component {
<div className="are-you-sure__header" id="are-you-sure__header"> <div className="are-you-sure__header" id="are-you-sure__header">
{i18n('ARE_YOU_SURE')} {i18n('ARE_YOU_SURE')}
</div> </div>
<span className="are-you-sure__close-icon" onClick={this.onNo.bind(this)}>
<Icon name="times" size="2x"/>
</span>
<div className="are-you-sure__description" id="are-you-sure__description"> <div className="are-you-sure__description" id="are-you-sure__description">
{this.props.description || (this.props.type === 'secure' && i18n('PLEASE_CONFIRM_PASSWORD'))} {this.props.description || (this.props.type === 'secure' && i18n('PLEASE_CONFIRM_PASSWORD'))}
</div> </div>
{(this.props.type === 'secure') ? this.renderPassword() : null} {(this.props.type === 'secure') ? this.renderPassword() : null}
<span className="separator" />
<div className="are-you-sure__buttons"> <div className="are-you-sure__buttons">
<div className="are-you-sure__yes-button">
<Button type="secondary" size="small" onClick={this.onYes.bind(this)} ref="yesButton" tabIndex="2">
{i18n('YES')}
</Button>
</div>
<div className="are-you-sure__no-button"> <div className="are-you-sure__no-button">
<Button type="link" size="auto" onClick={this.onNo.bind(this)} tabIndex="2"> <Button type="link" size="auto" onClick={this.onNo.bind(this)} tabIndex="2">
{i18n('CANCEL')} {i18n('CANCEL')}
</Button> </Button>
</div> </div>
<div className="are-you-sure__yes-button">
<Button type="secondary" size="small" onClick={this.onYes.bind(this)} ref="yesButton" tabIndex="2">
{i18n('YES')}
</Button>
</div>
</div> </div>
</div> </div>
); );
@ -62,7 +70,7 @@ class AreYouSure extends React.Component {
renderPassword() { renderPassword() {
return ( return (
<Input className="are-you-sure__password" password placeholder="password" name="password" value={this.state.password} onChange={this.onPasswordChange.bind(this)} onKeyDown={this.onInputKeyDown.bind(this)}/> <Input className="are-you-sure__password" password placeholder="password" name="password" ref="password" size="medium" value={this.state.password} onChange={this.onPasswordChange.bind(this)} onKeyDown={this.onInputKeyDown.bind(this)}/>
); );
} }
@ -79,7 +87,11 @@ class AreYouSure extends React.Component {
} }
onYes() { onYes() {
if (this.props.type === 'default' || this.state.password){ if (this.props.type === 'secure' && !this.state.password) {
this.refs.password.focus()
}
if (this.props.type === 'default' || this.state.password) {
this.closeModal(); this.closeModal();
if (this.props.onYes) { if (this.props.onYes) {

View File

@ -1,25 +1,30 @@
@import "../scss/vars"; @import "../scss/vars";
.are-you-sure { .are-you-sure {
width: 400px; width: 800px;
text-align: center; text-align: left;
&__header { &__header {
color: $primary-red; background-color: $secondary-blue;
font-size: $font-size--xl; border-top-right-radius: 4px;
border-top-left-radius: 4px;
color: white;
font-size: $font-size--lg;
font-weight: bold; font-weight: bold;
margin-bottom: 20px; margin-bottom: 10px;
padding: 10px;
} }
&__description { &__description {
color: $dark-grey; color: $dark-grey;
font-size: $font-size--md; font-size: $font-size--md;
margin-bottom: 50px; padding: 14px 5% 0;
} }
&__buttons { &__buttons {
margin: 0 auto; margin-top: 10px;
padding-bottom: 10px;
text-align: right;
} }
&__yes-button, &__yes-button,
@ -30,7 +35,25 @@
&__password { &__password {
margin: 0 auto; margin: 0 auto;
margin-top: -30px; margin-top: 20px;
margin-bottom: 20px; }
&__close-icon {
cursor: pointer;
position: absolute;
top: 10px;
right: 10px;
color: white;
}
.separator {
width: 90%;
margin: 30px auto;
}
}
@media screen and (max-width: 800px) {
.are-you-sure {
width: auto;
} }
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import keyCode from 'keycode'; import keyCode from 'keycode';
import classNames from 'classnames';
import store from 'app/store'; import store from 'app/store';
import ModalActions from 'actions/modal-actions'; import ModalActions from 'actions/modal-actions';
@ -8,11 +9,12 @@ import Modal from 'core-components/modal';
class ModalContainer extends React.Component { class ModalContainer extends React.Component {
static openModal(content) { static openModal(content, noPadding) {
store.dispatch( store.dispatch(
ModalActions.openModal( ModalActions.openModal({
content content,
) noPadding
})
); );
} }
@ -48,7 +50,7 @@ class ModalContainer extends React.Component {
renderModal() { renderModal() {
return ( return (
<Modal content={this.props.modal.content} /> <Modal content={this.props.modal.content} noPadding={this.props.modal.noPadding}/>
); );
} }

View File

@ -13,7 +13,7 @@ class PeopleList extends React.Component {
name: React.PropTypes.node, name: React.PropTypes.node,
assignedTickets: React.PropTypes.number, assignedTickets: React.PropTypes.number,
closedTickets: React.PropTypes.number, closedTickets: React.PropTypes.number,
lastLogin: React.PropTypes.number lastLogin: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string])
})), })),
pageSize: React.PropTypes.number, pageSize: React.PropTypes.number,
page: React.PropTypes.number, page: React.PropTypes.number,

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import _ from 'lodash'; import _ from 'lodash';
import RichTextEditor from 'react-rte-browserify'; import RichTextEditor from 'react-rte-browserify';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call'; import API from 'lib-app/api-call';
@ -190,7 +191,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"/> {(this.props.allowAttachments) ? <FormField name="file" field="file"/> : null}
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton> <SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
</Form> </Form>
</div> </div>
@ -333,7 +334,9 @@ class TicketViewer extends React.Component {
onCommentSuccess() { onCommentSuccess() {
this.setState({ this.setState({
loading: false, loading: false,
commentError: false commentValue: RichTextEditor.createEmptyValue(),
commentError: false,
commentEdited: false
}); });
this.onTicketModification(); this.onTicketModification();
@ -353,4 +356,8 @@ class TicketViewer extends React.Component {
} }
} }
export default TicketViewer; export default connect((store) => {
return {
allowAttachments: store.config['allow-attachments']
};
})(TicketViewer);

View File

@ -23,11 +23,11 @@ class AdminPanelMyAccount extends React.Component {
getEditorProps() { getEditorProps() {
return { return {
myAccount: true, myAccount: true,
staffId: this.props.userId, staffId: this.props.userId * 1,
name: this.props.userName, name: this.props.userName,
email: this.props.userEmail, email: this.props.userEmail,
profilePic: this.props.userProfilePic, profilePic: this.props.userProfilePic,
level: this.props.userLevel, level: this.props.userLevel * 1,
departments: this.props.userDepartments, departments: this.props.userDepartments,
onChange: () => this.props.dispatch(SessionActions.getUserData(null, null, true)) onChange: () => this.props.dispatch(SessionActions.getUserData(null, null, true))
}; };

View File

@ -8,7 +8,7 @@ class AdminPanelStats extends React.Component {
render() { render() {
return ( return (
<div class="admin-panel-stats"> <div className="admin-panel-stats">
<Header title={i18n('STATISTICS')} description={i18n('STATISTICS_DESCRIPTION')}/> <Header title={i18n('STATISTICS')} description={i18n('STATISTICS_DESCRIPTION')}/>
<Stats type="general"/> <Stats type="general"/>
</div> </div>

View File

@ -213,15 +213,15 @@ class AdminPanelSystemPreferences extends React.Component {
'reCaptchaPrivate': result.data.reCaptchaPrivate, 'reCaptchaPrivate': result.data.reCaptchaPrivate,
'url': result.data['url'], 'url': result.data['url'],
'title': result.data['title'], 'title': result.data['title'],
'layout': result.data['layout'] == 'full-width' ? 1 : 0, 'layout': (result.data['layout'] == 'full-width') ? 1 : 0,
'time-zone': result.data['time-zone'], 'time-zone': result.data['time-zone'],
'no-reply-email': result.data['no-reply-email'], 'no-reply-email': result.data['no-reply-email'],
'smtp-host': result.data['smtp-host'], 'smtp-host': result.data['smtp-host'],
'smtp-port': result.data['smtp-port'], 'smtp-port': result.data['smtp-port'],
'smtp-user': result.data['smtp-user'], 'smtp-user': result.data['smtp-user'],
'smtp-pass': '', 'smtp-pass': '',
'maintenance-mode': result.data['maintenance-mode'], 'maintenance-mode': !!(result.data['maintenance-mode'] * 1),
'allow-attachments': result.data['allow-attachments'], 'allow-attachments': !!(result.data['allow-attachments'] * 1),
'max-size': result.data['max-size'], 'max-size': result.data['max-size'],
'allowedLanguages': result.data.allowedLanguages.map(lang => (_.indexOf(languageKeys, lang))), 'allowedLanguages': result.data.allowedLanguages.map(lang => (_.indexOf(languageKeys, lang))),
'supportedLanguages': result.data.supportedLanguages.map(lang => (_.indexOf(languageKeys, lang))) 'supportedLanguages': result.data.supportedLanguages.map(lang => (_.indexOf(languageKeys, lang)))

View File

@ -9,6 +9,7 @@
&__update-name-button { &__update-name-button {
float: left; float: left;
min-width: 156px;
} }
&__optional-buttons { &__optional-buttons {

View File

@ -68,9 +68,9 @@ class AdminPanelStaffMembers extends React.Component {
if(!this.state.selectedDepartment) { if(!this.state.selectedDepartment) {
staffList = this.state.staffList; staffList = this.state.staffList;
} else { } else {
staffList = _.filter(this.state.staffList, (o) => { staffList = _.filter(this.state.staffList, (staff) => {
return _.findIndex(o.departments, {id: this.state.selectedDepartment}) !== -1; return _.findIndex(staff.departments, {id: this.state.selectedDepartment}) !== -1;
}) });
} }
return staffList.map(staff => { return staffList.map(staff => {

View File

@ -31,6 +31,10 @@ class StaffEditor extends React.Component {
onDelete: React.PropTypes.func onDelete: React.PropTypes.func
}; };
static defaultProps = {
tickets: []
};
state = { state = {
email: this.props.email, email: this.props.email,
level: this.props.level - 1, level: this.props.level - 1,

View File

@ -151,8 +151,8 @@ class AdminPanelListUsers extends React.Component {
onUsersRetrieved(result) { onUsersRetrieved(result) {
this.setState({ this.setState({
page: result.data.page, page: result.data.page * 1,
pages: result.data.pages, pages: result.data.pages * 1,
users: result.data.users, users: result.data.users,
orderBy: result.data.orderBy, orderBy: result.data.orderBy,
desc: (result.data.desc === '1'), desc: (result.data.desc === '1'),

View File

@ -153,9 +153,7 @@ let DemoPage = React.createClass({
title: 'ModalTrigger', title: 'ModalTrigger',
render: ( render: (
<Button onClick={function () { <Button onClick={function () {
ModalContainer.openModal( AreYouSure.openModal('I confirm I want to perform this action.', ()=> {alert('yes');}, 'secure')
<AreYouSure description="I confirm I want to perform this action." onYes={()=> {alert('yes');}} />
);
}}> }}>
Open Modal Open Modal
</Button> </Button>

View File

@ -3,7 +3,6 @@ import React from 'react';
import API from 'lib-app/api-call'; import API from 'lib-app/api-call';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
import ModalContainer from 'app-components/modal-container';
import AreYouSure from 'app-components/are-you-sure'; import AreYouSure from 'app-components/are-you-sure';
import Header from 'core-components/header'; import Header from 'core-components/header';
@ -64,11 +63,11 @@ class DashboardEditProfilePage extends React.Component {
} }
} }
onSubmitEditEmail(formState) { onSubmitEditEmail(formState) {
ModalContainer.openModal(<AreYouSure onYes={this.callEditEmailAPI.bind(this, formState)}/>); AreYouSure.openModal(i18n('EMAIL_WILL_CHANGE'), this.callEditEmailAPI.bind(this, formState));
} }
onSubmitEditPassword(formState) { onSubmitEditPassword(formState) {
ModalContainer.openModal(<AreYouSure onYes={this.callEditPassAPI.bind(this, formState)}/>); AreYouSure.openModal(i18n('PASSWORD_WILL_CHANGE'), this.callEditPassAPI.bind(this, formState));
} }
callEditEmailAPI(formState){ callEditEmailAPI(formState){

View File

@ -32,7 +32,7 @@ class MainLayoutHeader extends React.Component {
result = ( result = (
<div className="main-layout-header__login-links"> <div className="main-layout-header__login-links">
<Button type="clean" route={{to:'/'}}>{i18n('LOG_IN')}</Button> <Button type="clean" route={{to:'/'}}>{i18n('LOG_IN')}</Button>
{(this.props.config['registration']) ? <Button type="clean" route={{to:'/signup'}}>{i18n('SIGN_UP')}</Button> : null} {(!!(this.props.config['registration'] * 1)) ? <Button type="clean" route={{to:'/signup'}}>{i18n('SIGN_UP')}</Button> : null}
</div> </div>
); );
} }

View File

@ -1,10 +1,12 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import {Motion, spring} from 'react-motion'; import {Motion, spring} from 'react-motion';
class Modal extends React.Component { class Modal extends React.Component {
static propTypes = { static propTypes = {
content: React.PropTypes.node content: React.PropTypes.node,
noPadding: React.PropTypes.bool
}; };
render() { render() {
@ -30,13 +32,22 @@ class Modal extends React.Component {
renderModal(animation) { renderModal(animation) {
return ( return (
<div className="modal" style={{opacity: animation.fade}}> <div className={this.getClass()} style={{opacity: animation.fade}}>
<div className="modal__content" style={{transform: 'scale(' + animation.scale + ')'}}> <div className="modal__content" style={{transform: 'scale(' + animation.scale + ')'}}>
{this.props.content} {this.props.content}
</div> </div>
</div> </div>
) )
} }
getClass() {
let classes = {
'modal': true,
'modal_no-padding': this.props.noPadding
};
return classNames(classes);
}
} }
export default Modal; export default Modal;

View File

@ -18,4 +18,11 @@
padding: 50px; padding: 50px;
box-shadow: 0 0 10px white; box-shadow: 0 0 10px white;
} }
&_no-padding {
.modal__content {
padding: 0;
}
}
} }

View File

@ -281,7 +281,9 @@ export default {
'TICKET_SENT': 'Ticket has been created successfully.', 'TICKET_SENT': 'Ticket has been created successfully.',
'VALID_RECOVER': 'Password recovered successfully', 'VALID_RECOVER': 'Password recovered successfully',
'EMAIL_EXISTS': 'Email already exists', 'EMAIL_EXISTS': 'Email already exists',
'ARE_YOU_SURE': 'Are you sure?', 'ARE_YOU_SURE': 'Confirm action',
'EMAIL_WILL_CHANGE': 'The current email will be changed',
'PASSWORD_WILL_CHANGE': 'The current password will be changed',
'EMAIL_CHANGED': 'Email has been changed successfully', 'EMAIL_CHANGED': 'Email has been changed successfully',
'PASSWORD_CHANGED': 'Password has been changed successfully', 'PASSWORD_CHANGED': 'Password has been changed successfully',
'OLD_PASSWORD_INCORRECT': 'Old password is incorrect', 'OLD_PASSWORD_INCORRECT': 'Old password is incorrect',

View File

@ -29,6 +29,7 @@ function processData (data, dataAsForm = false) {
module.exports = { module.exports = {
call: function ({path, data, plain, dataAsForm}) { call: function ({path, data, plain, dataAsForm}) {
console.log('request ' + path, data);
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
APIUtils.post(apiUrl + path, processData(data, dataAsForm), dataAsForm) APIUtils.post(apiUrl + path, processData(data, dataAsForm), dataAsForm)
.then(function (result) { .then(function (result) {

View File

@ -62,6 +62,7 @@ class SessionStore {
this.setItem('title', configs.title); this.setItem('title', configs.title);
this.setItem('registration', configs.registration); this.setItem('registration', configs.registration);
this.setItem('user-system-enabled', configs['user-system-enabled']); this.setItem('user-system-enabled', configs['user-system-enabled']);
this.setItem('allow-attachments', configs['allow-attachments']);
} }
getConfigs() { getConfigs() {
@ -72,9 +73,11 @@ class SessionStore {
allowedLanguages: JSON.parse(this.getItem('allowedLanguages')), allowedLanguages: JSON.parse(this.getItem('allowedLanguages')),
supportedLanguages: JSON.parse(this.getItem('supportedLanguages')), supportedLanguages: JSON.parse(this.getItem('supportedLanguages')),
layout: this.getItem('layout'), layout: this.getItem('layout'),
registration: this.getItem('registration'),
title: this.getItem('title'), title: this.getItem('title'),
['user-system-enabled']: this.getItem('user-system-enabled') 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)
}; };
} }

View File

@ -26,7 +26,6 @@ class AdminDataReducer extends Reducer {
getTypeHandlers() { getTypeHandlers() {
return { return {
'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses, 'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses,
'SESSION_CHECKED': this.onSessionChecked,
'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved, 'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved,
'MY_TICKETS_REJECTED': this.onMyTicketsRejected, 'MY_TICKETS_REJECTED': this.onMyTicketsRejected,
@ -51,15 +50,6 @@ class AdminDataReducer extends Reducer {
}); });
} }
onSessionChecked(state) {
const customResponses = sessionStore.getItem('customResponses');
return _.extend({}, state, {
customResponses: JSON.parse(customResponses),
customResponsesLoaded: true
});
}
onMyTicketsRetrieved(state, payload) { onMyTicketsRetrieved(state, payload) {
return _.extend({}, state, { return _.extend({}, state, {
myTickets: payload.data, myTickets: payload.data,

View File

@ -37,6 +37,10 @@ class ConfigReducer extends Reducer {
return _.extend({}, state, payload.data, { return _.extend({}, state, payload.data, {
language: currentLanguage || payload.language, language: currentLanguage || payload.language,
registration: !!(payload.data.registration * 1),
'user-system-enabled': !!(payload.data['user-system-enabled']* 1),
'allow-attachments': !!(payload.data['allow-attachments']* 1),
'maintenance-mode': !!(payload.data['maintenance-mode']* 1),
initDone: true initDone: true
}); });
} }

View File

@ -7,6 +7,7 @@ class ModalReducer extends Reducer {
getInitialState() { getInitialState() {
return { return {
opened: false, opened: false,
noPadding: false,
content: null content: null
}; };
} }
@ -23,7 +24,8 @@ class ModalReducer extends Reducer {
return _.extend({}, state, { return _.extend({}, state, {
opened: true, opened: true,
content: payload content: payload.content,
noPadding: payload.noPadding || false
}); });
} }
@ -32,7 +34,8 @@ class ModalReducer extends Reducer {
return _.extend({}, state, { return _.extend({}, state, {
opened: false, opened: false,
content: null content: null,
noPadding: false
}); });
} }
} }

View File

@ -28,8 +28,11 @@ class LastEventsStaffController extends Controller {
$query = substr($query,0,-3); $query = substr($query,0,-3);
$query .= ') ORDER BY id desc LIMIT ? OFFSET ?' ; $query .= ') ORDER BY id desc LIMIT ? OFFSET ?' ;
$eventList = Ticketevent::find($query, [10, 10*($page-1)]); if(Ticketevent::count() && !$user->sharedTicketList->isEmpty()) {
$eventList = Ticketevent::find($query, [10, 10*($page-1)]);
Response::respondSuccess($eventList->toArray()); Response::respondSuccess($eventList->toArray());
} else {
Response::respondSuccess([]);
}
} }
} }

View File

@ -15,7 +15,7 @@ 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/add-api-key.php';
require_once 'system/delete-api-key.php'; require_once 'system/delete-api-key.php';
require_once 'system/get-all-keys.php'; require_once 'system/get-api-keys.php';
require_once 'system/get-stats.php'; require_once 'system/get-stats.php';
require_once 'system/delete-all-users.php'; require_once 'system/delete-all-users.php';
require_once 'system/csv-import.php'; require_once 'system/csv-import.php';
@ -40,7 +40,7 @@ $systemControllerGroup->addController(new EnableRegistrationController);
$systemControllerGroup->addController(new GetStatsController); $systemControllerGroup->addController(new GetStatsController);
$systemControllerGroup->addController(new AddAPIKeyController); $systemControllerGroup->addController(new AddAPIKeyController);
$systemControllerGroup->addController(new DeleteAPIKeyController); $systemControllerGroup->addController(new DeleteAPIKeyController);
$systemControllerGroup->addController(new GetAllKeyController); $systemControllerGroup->addController(new GetAPIKeysController);
$systemControllerGroup->addController(new DeleteAllUsersController); $systemControllerGroup->addController(new DeleteAllUsersController);
$systemControllerGroup->addController(new BackupDatabaseController); $systemControllerGroup->addController(new BackupDatabaseController);
$systemControllerGroup->addController(new DownloadController); $systemControllerGroup->addController(new DownloadController);

View File

@ -1,8 +1,8 @@
<?php <?php
use Respect\Validation\Validator as DataValidator; use Respect\Validation\Validator as DataValidator;
class GetAllKeyController extends Controller { class GetAPIKeysController extends Controller {
const PATH = '/get-all-keys'; const PATH = '/get-api-keys';
const METHOD = 'POST'; const METHOD = 'POST';
public function validations() { public function validations() {

View File

@ -86,6 +86,6 @@ class TicketGetController extends Controller {
$user = Controller::getLoggedUser(); $user = Controller::getLoggedUser();
return (!Controller::isStaffLogged() && (Controller::isUserSystemEnabled() && $this->ticket->author->id !== $user->id)) || return (!Controller::isStaffLogged() && (Controller::isUserSystemEnabled() && $this->ticket->author->id !== $user->id)) ||
(Controller::isStaffLogged() && $this->ticket->owner && $this->ticket->owner->id !== $user->id); (Controller::isStaffLogged() && !$user->sharedDepartmentList->includesId($this->ticket->department->id));
} }
} }

View File

@ -30,6 +30,11 @@ class DeleteUserController extends Controller {
Log::createLog('DELETE_USER', $user->name); Log::createLog('DELETE_USER', $user->name);
RedBean::exec('DELETE FROM log WHERE author_user_id = ?', [$userId]); RedBean::exec('DELETE FROM log WHERE author_user_id = ?', [$userId]);
foreach($user->sharedTicketList as $ticket) {
$ticket->delete();
}
$user->delete(); $user->delete();
Response::respondSuccess(); Response::respondSuccess();

View File

@ -24,6 +24,11 @@ class LoginController extends Controller {
} }
if ($this->checkInputCredentials() || $this->checkRememberToken()) { if ($this->checkInputCredentials() || $this->checkRememberToken()) {
if($this->userInstance->verificationToken !== null) {
Response::respondError(ERRORS::UNVERIFIED_USER);
return;
}
$this->createUserSession(); $this->createUserSession();
$this->createSessionCookie(); $this->createSessionCookie();
if(Controller::request('staff')) { if(Controller::request('staff')) {
@ -31,14 +36,6 @@ class LoginController extends Controller {
$this->userInstance->store(); $this->userInstance->store();
} }
$email = Controller::request('email');
$userRow = User::getDataStore($email, 'email');
if($userRow->verificationToken !== null) {
Response::respondError(ERRORS::UNVERIFIED_USER);
return;
}
Response::respondSuccess($this->getUserData()); Response::respondSuccess($this->getUserData());
} else { } else {
Response::respondError(ERRORS::INVALID_CREDENTIALS); Response::respondError(ERRORS::INVALID_CREDENTIALS);

View File

@ -79,7 +79,9 @@ abstract class Controller {
} }
public function uploadFile() { public function uploadFile() {
if(!isset($_FILES['file'])) return ''; $allowAttachments = Setting::getSetting('allow-attachments')->getValue();
if(!isset($_FILES['file']) || !$allowAttachments) return '';
$maxSize = Setting::getSetting('max-size')->getValue(); $maxSize = Setting::getSetting('max-size')->getValue();
$fileGap = Setting::getSetting('file-gap')->getValue(); $fileGap = Setting::getSetting('file-gap')->getValue();

View File

@ -45,6 +45,10 @@ class DataStoreList implements IteratorAggregate {
return $includes; return $includes;
} }
public function isEmpty() {
return empty($list);
}
public function toBeanList() { public function toBeanList() {
$beanList = []; $beanList = [];

View File

@ -55,7 +55,7 @@ require './system/disable-registration.rb'
require './system/enable-registration.rb' require './system/enable-registration.rb'
require './system/add-api-key.rb' require './system/add-api-key.rb'
require './system/delete-api-key.rb' require './system/delete-api-key.rb'
require './system/get-all-keys.rb' require './system/get-api-keys.rb'
require './system/file-upload-download.rb' require './system/file-upload-download.rb'
require './system/csv-import.rb' require './system/csv-import.rb'
require './system/disable-user-system.rb' require './system/disable-user-system.rb'

View File

@ -1,15 +1,15 @@
describe'system/get-all-keys' do describe'system/get-api-keys' do
request('/user/logout') request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true) Scripts.login($staff[:email], $staff[:password], true)
it 'should get all API keys' do it 'should get all API keys' do
Scripts.createAPIKey('namekey1') Scripts.createAPIKey('namekey1')
Scripts.createAPIKey('namekey2') Scripts.createAPIKey('namekey2')
Scripts.createAPIKey('namekey3') Scripts.createAPIKey('namekey3')
Scripts.createAPIKey('namekey4') Scripts.createAPIKey('namekey4')
Scripts.createAPIKey('namekey5') Scripts.createAPIKey('namekey5')
result= request('/system/get-all-keys', { result = request('/system/get-api-keys', {
csrf_userid: $csrf_userid, csrf_userid: $csrf_userid,
csrf_token: $csrf_token, csrf_token: $csrf_token,
}) })