diff --git a/client/package.json b/client/package.json index 21e3f053..0033b6df 100644 --- a/client/package.json +++ b/client/package.json @@ -54,6 +54,7 @@ }, "dependencies": { "app-module-path": "^1.0.3", + "chart.js": "^2.4.0", "classnames": "^2.1.3", "draft-js": "^0.8.1", "draft-js-export-html": "^0.4.0", @@ -63,6 +64,7 @@ "lodash": "^3.10.0", "messageformat": "^0.2.2", "react": "^15.0.1", + "react-chartjs-2": "^2.0.0", "react-document-title": "^1.0.2", "react-dom": "^15.0.1", "react-google-recaptcha": "^0.5.2", diff --git a/client/src/app-components/stats-chart.js b/client/src/app-components/stats-chart.js new file mode 100644 index 00000000..ca1a2d49 --- /dev/null +++ b/client/src/app-components/stats-chart.js @@ -0,0 +1,100 @@ +import React from 'react'; +import {Line} from 'react-chartjs-2'; + +import i18n from 'lib-app/i18n'; + +class StatsChart extends React.Component { + + static propTypes = { + strokes: React.PropTypes.arrayOf(React.PropTypes.shape({ + name: React.PropTypes.string, + values: React.PropTypes.arrayOf(React.PropTypes.shape({ + date: React.PropTypes.string, + value: React.PropTypes.number + })) + })), + period: React.PropTypes.number + }; + + render() { + return ( +
+ +
+ ); + } + + getChartData() { + let labels = this.getLabels(); + + let color = { + 'CLOSE': 'rgba(150, 20, 20, 0.6)', + 'CREATE_TICKET': 'rgba(20, 150, 20, 0.6)', + 'SIGNUP': 'rgba(20, 20, 150, 0.6)', + 'COMMENT': 'rgba(20, 200, 200, 0.6)', + 'ASSIGN': 'rgba(20, 150, 20, 0.6)' + }; + + let strokes = this.props.strokes.slice(); + let datasets = strokes.map((stroke, index) => { + return { + label: i18n('CHART_' + stroke.name), + data: stroke.values.map((val) => val.value), + fill: false, + borderWidth: this.getBorderWidth(), + borderColor: color[stroke.name], + pointBorderColor: color[stroke.name], + pointRadius: 0, + pointHoverRadius: 3, + lineTension: 0.2, + pointHoverBackgroundColor: color[stroke.name], + hitRadius: this.hitRadius(index) + } + }); + + return { + labels: labels, + datasets: datasets + }; + } + + getBorderWidth() { + return (this.props.period <= 90) ? 3 : 2; + } + + getLabels() { + let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + let labels = []; + + if (!this.props.strokes.length) { + labels = Array.from('x'.repeat(this.props.period)); + } + else { + labels = this.props.strokes[0].values.map((item) => { + let idx = item.date.slice(4, 6) - 1; + + return item.date.slice(6, 8) + ' ' + months[idx]; + }); + } + + return labels; + } + + hitRadius(index) { + if (this.props.period <= 7) return 20; + if (this.props.period <= 30) return 15; + if (this.props.period <= 90) return 10; + return 3; + } + + getChartOptions() { + return { + legend: { + display: false + } + }; + } +} + + +export default StatsChart; \ No newline at end of file diff --git a/client/src/app-components/stats.js b/client/src/app-components/stats.js new file mode 100644 index 00000000..a93e67dd --- /dev/null +++ b/client/src/app-components/stats.js @@ -0,0 +1,197 @@ +import React from 'react'; +import _ from 'lodash'; +import classNames from 'classnames'; + +import i18n from 'lib-app/i18n'; +import API from 'lib-app/api-call'; + +import DropDown from 'core-components/drop-down'; +import ToggleList from 'core-components/toggle-list'; + +import StatsChart from 'app-components/stats-chart'; + +const generalStrokes = ['CREATE_TICKET', 'CLOSE', 'SIGNUP', 'COMMENT']; +const staffStrokes = ['ASSIGN', 'CLOSE']; +const ID = { + 'CREATE_TICKET': 0, + 'ASSIGN': 0, + 'CLOSE': 1, + 'SIGNUP': 2, + 'COMMENT': 3 +}; + +class Stats extends React.Component { + + static propTypes = { + type: React.PropTypes.string, + staffId: React.PropTypes.number + }; + + state = { + stats: this.getDefaultStats(), + strokes: this.getStrokes().map((name) => { + return { + name: name, + values: [] + } + }), + showed: [0], + period: 0 + }; + + componentDidMount() { + this.retrieve(7); + } + + render() { + return ( +
+ + + +
+ ); + } + + getClass() { + let classes = { + 'stats': true, + 'stats_staff': this.props.type === 'staff' + }; + + return classNames(classes); + } + + getToggleListProps() { + return { + values: this.state.showed, + className: 'stats__toggle-list', + onChange: this.onToggleListChange.bind(this), + type: this.props.type === 'general' ? 'default' : 'small', + items: this.getStrokes().map((name) => { + return { + content: +
+
{this.state.stats[name]}
+
{i18n('CHART_' + name)}
+
+ } + }) + }; + } + + onToggleListChange(event) { + this.setState({ + showed: event.target.value + }); + } + + getDropDownProps() { + return { + items: ['Last 7 days', 'Last 30 days', 'Last 90 days', 'Last 365 days'].map((name) => { + return { + content: name, + icon: '' + }; + }), + onChange: this.onDropDownChange.bind(this), + className: 'stats__dropdown' + } + } + + onDropDownChange(event) { + let val = [7, 30, 90, 365]; + + this.retrieve(val[event.index]); + } + + getStatsChartProps() { + let showed = this.getShowedArray(); + + return { + period: this.state.period, + strokes: _.filter(this.state.strokes, (s, i) => showed[i]) + }; + } + + retrieve(period) { + let periodName; + + switch (period) { + case 30: + periodName = 'MONTH'; + break; + case 90: + periodName = 'QUARTER'; + break; + case 365: + periodName = 'YEAR'; + break; + default: + periodName = 'WEEK'; + } + + API.call({ + path: '/system/get-stats', + data: this.getApiCallData(periodName) + }).then(this.onRetrieveSuccess.bind(this, period)); + } + + onRetrieveSuccess(period, result) { + let newStats = this.getDefaultStats(); + + let newStrokes = this.getStrokes().map((name) => { + return { + name: name, + values: [] + }; + }); + + let realPeriod = result.data.length / this.getStrokes().length; + + result.data.map((item) => { + newStats[item.type] += item.value * 1; + + newStrokes[ ID[item.type] ].values.push({ + date: item.date, + value: item.value * 1 + }); + }); + + this.setState({stats: newStats, strokes: newStrokes, period: realPeriod}); + } + + getShowedArray() { + let showed = this.getStrokes().map(() => false); + + for (let i = 0; i < showed.length; i++) { + showed[this.state.showed[i]] = true; + } + + return showed; + } + + getStrokes() { + return this.props.type === 'general' ? generalStrokes : staffStrokes; + } + + getDefaultStats() { + return this.props.type === 'general' ? + { + 'CREATE_TICKET': 0, + 'CLOSE': 0, + 'SIGNUP': 0, + 'COMMENT': 0 + } : + { + 'ASSIGN': 0, + 'CLOSE': 0 + }; + } + + getApiCallData(periodName) { + return this.props.type === 'general' ? {period: periodName} : {period: periodName, staffId: this.props.staffId}; + } +} + +export default Stats; \ No newline at end of file diff --git a/client/src/app-components/stats.scss b/client/src/app-components/stats.scss new file mode 100644 index 00000000..c1557e9e --- /dev/null +++ b/client/src/app-components/stats.scss @@ -0,0 +1,54 @@ +@import '../scss/vars'; + +.stats { + + &__dropdown { + margin-left: auto; + margin-bottom: 20px; + } + + &__toggle-list { + margin-bottom: 20px; + user-select: none; + + &-item { + + &-value { + font-size: $font-size--lg; + line-height: 80px; + } + + &-name { + font-size: $font-size--md; + line-height: 20px; + } + } + } + + &_staff { + .stats__dropdown { + margin-left: auto; + margin-bottom: 20px; + float: left; + } + + .stats__toggle-list { + margin-bottom: 20px; + float: right; + + &-item { + + &-value { + font-size: $font-size--md; + line-height: 40px; + } + + &-name { + font-size: $font-size--sm; + line-height: 20px; + } + } + } + } + +} \ No newline at end of file diff --git a/client/src/app/App.js b/client/src/app/App.js index d52ea248..6d327fa8 100644 --- a/client/src/app/App.js +++ b/client/src/app/App.js @@ -86,20 +86,24 @@ class App extends React.Component { browserHistory.push('/admin/panel'); } - if (this.props.session.userLevel && !this.isPathAvailableForStaff()) { + if (props.session.userLevel && !this.isPathAvailableForStaff(props)) { browserHistory.push('/admin/panel'); } + + if (!props.config.registration && _.includes(props.location.pathname, 'signup')) { + browserHistory.push('/'); + } } - isPathAvailableForStaff() { - let pathForLevel2 = _.findIndex(level2Paths, path => _.includes(this.props.location.pathname, path)) !== -1; - let pathForLevel3 = _.findIndex(level3Paths, path => _.includes(this.props.location.pathname, path)) !== -1; + isPathAvailableForStaff(props) { + let pathForLevel2 = _.findIndex(level2Paths, path => _.includes(props.location.pathname, path)) !== -1; + let pathForLevel3 = _.findIndex(level3Paths, path => _.includes(props.location.pathname, path)) !== -1; - if (this.props.session.userLevel === 1) { + if (props.session.userLevel === 1) { return !pathForLevel2 && !pathForLevel3; } - if (this.props.session.userLevel === 2) { + if (props.session.userLevel === 2) { return !pathForLevel3; } diff --git a/client/src/app/admin/panel/admin-panel-menu.js b/client/src/app/admin/panel/admin-panel-menu.js index 7112320d..976d7c96 100644 --- a/client/src/app/admin/panel/admin-panel-menu.js +++ b/client/src/app/admin/panel/admin-panel-menu.js @@ -99,7 +99,7 @@ class AdminPanelMenu extends React.Component { level: 1, items: this.getItemsByFilteredByLevel([ { - name: i18n('TICKET_STATS'), + name: i18n('STATISTICS'), path: '/admin/panel/stats', level: 1 }, diff --git a/client/src/app/admin/panel/dashboard/admin-panel-my-account.js b/client/src/app/admin/panel/dashboard/admin-panel-my-account.js index 0cd165e3..165db779 100644 --- a/client/src/app/admin/panel/dashboard/admin-panel-my-account.js +++ b/client/src/app/admin/panel/dashboard/admin-panel-my-account.js @@ -23,6 +23,7 @@ class AdminPanelMyAccount extends React.Component { getEditorProps() { return { myAccount: true, + staffId: this.props.userId, name: this.props.userName, email: this.props.userEmail, profilePic: this.props.userProfilePic, diff --git a/client/src/app/admin/panel/dashboard/admin-panel-stats.js b/client/src/app/admin/panel/dashboard/admin-panel-stats.js index 020264e4..c5452e50 100644 --- a/client/src/app/admin/panel/dashboard/admin-panel-stats.js +++ b/client/src/app/admin/panel/dashboard/admin-panel-stats.js @@ -1,14 +1,20 @@ import React from 'react'; +import i18n from 'lib-app/i18n'; +import Stats from 'app-components/stats'; +import Header from 'core-components/header'; + class AdminPanelStats extends React.Component { render() { return ( -
- /admin/panel/stats +
+
+
); } + } export default AdminPanelStats; \ No newline at end of file diff --git a/client/src/app/admin/panel/dashboard/admin-panel-stats.scss b/client/src/app/admin/panel/dashboard/admin-panel-stats.scss new file mode 100644 index 00000000..e69de29b diff --git a/client/src/app/admin/panel/staff/staff-editor.js b/client/src/app/admin/panel/staff/staff-editor.js index fdca49f0..1581698f 100644 --- a/client/src/app/admin/panel/staff/staff-editor.js +++ b/client/src/app/admin/panel/staff/staff-editor.js @@ -6,6 +6,7 @@ import API from 'lib-app/api-call'; import SessionStore from 'lib-app/session-store'; import TicketList from 'app-components/ticket-list'; import AreYouSure from 'app-components/are-you-sure'; +import Stats from 'app-components/stats'; import Form from 'core-components/form'; import FormField from 'core-components/form-field'; @@ -97,7 +98,8 @@ class StaffEditor extends React.Component {
- ACTIVITY +
{i18n('ACTIVITY')}
+
diff --git a/client/src/app/admin/panel/staff/staff-editor.scss b/client/src/app/admin/panel/staff/staff-editor.scss index 8e29ab3c..2f601ddd 100644 --- a/client/src/app/admin/panel/staff/staff-editor.scss +++ b/client/src/app/admin/panel/staff/staff-editor.scss @@ -156,4 +156,13 @@ color: $dark-grey; } } + + &__activity { + + &-title { + margin-bottom: 10px; + text-align: left; + } + } + } \ No newline at end of file diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 5244975c..68042fd4 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -1,6 +1,7 @@ 'use strict'; const React = require('react'); +const LineChart = require("react-chartjs-2").Line; const _ = require('lodash'); const DocumentTitle = require('react-document-title'); @@ -16,8 +17,42 @@ const Menu = require('core-components/menu'); const Tooltip = require('core-components/tooltip'); const Table = require('core-components/table'); const InfoTooltip = require('core-components/info-tooltip'); -const ToggleButton = require('app-components/toggle-button'); +function rand(min, max, num) { + var rtn = []; + while (rtn.length < num) { + rtn.push((Math.random() * (max - min)) + min); + } + return rtn; +} + +let chartData = { + labels: ["January", "February", "March", "April", "May", "June"], + datasets: [ + { + label: "My Second dataset", + fill: false, + pointColor: "rgba(151,187,205,1)", + pointStrokeColor: "#fff", + pointHighlightFill: "#fff", + pointHighlightStroke: "rgba(151,187,205,1)", + borderWidth: 3, + data: rand(32, 100, 6), + pointRadius: 0 + }, + { + label: "My Second dataset", + fill: false, + pointColor: "rgba(151,187,205,1)", + pointStrokeColor: "#fff", + pointHighlightFill: "#fff", + pointHighlightStroke: "rgba(151,187,205,1)", + borderWidth: 3, + data: rand(32, 100, 6) + } + ] +}; +let chartOptions = {}; let dropDownItems = [{content: 'English'}, {content: 'Spanish'}, {content: 'German'}, {content: 'Portuguese'}, {content: 'Japanese'}]; let secondaryMenuItems = [ {content: 'My Tickets', icon: 'file-text'}, @@ -176,6 +211,12 @@ let DemoPage = React.createClass({ render: ( ) + }, + { + title: 'LineChart', + render: ( + + ) } ], diff --git a/client/src/app/main/main-layout-header.js b/client/src/app/main/main-layout-header.js index 6702eb0b..c42545f0 100644 --- a/client/src/app/main/main-layout-header.js +++ b/client/src/app/main/main-layout-header.js @@ -32,7 +32,7 @@ class MainLayoutHeader extends React.Component { result = (
- + {this.props.config === true ? : null}
); } diff --git a/client/src/core-components/toggle-list.js b/client/src/core-components/toggle-list.js index 8dba39a7..edf16ada 100644 --- a/client/src/core-components/toggle-list.js +++ b/client/src/core-components/toggle-list.js @@ -7,7 +7,8 @@ class ToggleList extends React.Component { items: React.PropTypes.arrayOf(React.PropTypes.shape({ content: React.PropTypes.node })), - onChange: React.PropTypes.func + onChange: React.PropTypes.func, + type: React.PropTypes.oneOf(['default', 'small']) }; state = { @@ -16,12 +17,22 @@ class ToggleList extends React.Component { render() { return ( -
+
{this.props.items.map(this.renderItem.bind(this))}
); } + getClass() { + let classes = { + 'toggle-list': true, + 'toggle-list_small': this.props.type == 'small', + [this.props.className]: (this.props.className) + }; + + return classNames(classes); + } + renderItem(obj, index) { return ( @@ -35,16 +46,17 @@ class ToggleList extends React.Component { let classes = { 'toggle-list__item': true, 'toggle-list__first-item': (index === 0), - 'toggle-list__selected': _.includes(this.state.selected, index) + 'toggle-list__last-item': (index === this.props.items.length - 1), + 'toggle-list__selected': _.includes(this.getSelectedList(), index) }; return classNames(classes); } selectItem(index) { - let newSelected = _.clone(this.state.selected); + let newSelected = _.clone(this.getSelectedList()); - _.includes(this.state.selected, index) ? _.remove(newSelected, _index => _index == index) : newSelected.push(index); + _.includes(this.getSelectedList(), index) ? _.remove(newSelected, _index => _index == index) : newSelected.push(index); this.setState({ selected: newSelected @@ -58,6 +70,10 @@ class ToggleList extends React.Component { }); } } + + getSelectedList() { + return (this.props.values === undefined) ? this.state.selected : this.props.values; + } } export default ToggleList; diff --git a/client/src/core-components/toggle-list.scss b/client/src/core-components/toggle-list.scss index 7bf244cb..1ba34f67 100644 --- a/client/src/core-components/toggle-list.scss +++ b/client/src/core-components/toggle-list.scss @@ -8,14 +8,28 @@ width: 180px; height: 120px; display: inline-block; - transition: background-color 0.4s ease; + transition: box-shadow 0.2s ease-in-out; + user-select: none; + cursor: default; } &__selected { - background-color: $light-grey; + box-shadow: inset 0 -5px 40px 10px rgba(0, 0, 0, 0.08); } &__first-item { border: 1px $light-grey solid; + border-radius: 4px 0 0 4px; + } + + &__last-item { + border-radius: 0 4px 4px 0; + } + + &_small { + .toggle-list__item { + height: 80px; + width: 120px; + } } } diff --git a/client/src/data/fixtures/system-fixtures.js b/client/src/data/fixtures/system-fixtures.js index b317c0b1..d98c393a 100644 --- a/client/src/data/fixtures/system-fixtures.js +++ b/client/src/data/fixtures/system-fixtures.js @@ -20,6 +20,7 @@ module.exports = [ 'smtp-user': 'Wesa', 'maintenance-mode': false, 'allow-attachments': true, + 'registration': true, 'max-size': 500, 'departments': [ {id: 1, name: 'Sales Support', owners: 2}, @@ -168,6 +169,71 @@ module.exports = [ }; } }, + { + path: '/system/get-stats', + time: 200, + response: function(_data) { + let generalVal = _data.staffId; + + let ID = { + 'WEEK': 7, + 'MONTH': 30, + 'QUARTER': 90, + 'YEAR': 365 + }; + + let k = ID[_data.period]; + let DATA = []; + + for (let i = 0; i < k; i++) { + if(generalVal){ + DATA.push({ + date: '201701' + (i + 103) % 100, + type: 'ASSIGN', + general: generalVal, + value: (Math.floor((Math.random() + 17) * i)).toString() + }); + DATA.push({ + date: '201701' + (i + 109) % 100, + type: 'CLOSE', + general: generalVal, + value: (Math.floor((Math.random() + 12) * i )).toString() + }); + } + else { + DATA.push({ + date: '201701' + (i + 107) % 100, + type: 'COMMENT', + general: generalVal, + value: (Math.floor((Math.random() + 5) * i)).toString() + }); + DATA.push({ + date: '201701' + (i + 104) % 100, + type: 'SIGNUP', + general: generalVal, + value: (Math.floor(Math.random() * (i - 180) * (i - 185) / 400)).toString() + }); + DATA.push({ + date: '201701' + (i + 103) % 100, + type: 'CLOSE', + general: generalVal, + value: (Math.floor((Math.random() + 12) * i )).toString() + }); + DATA.push({ + date: '201701' + (i + 99) % 100, + type: 'CREATE_TICKET', + general: generalVal, + value: (Math.floor((Math.random() + 7) * i)).toString() + }); + } + } + + return { + status: "success", + data: DATA + }; + } + }, { path: '/system/get-logs', time: 300, diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 882ffabf..81277ea4 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -36,7 +36,7 @@ export default { 'DASHBOARD': 'Dashboard', 'USERS': 'Users', 'SETTINGS': 'Settings', - 'TICKET_STATS': 'Ticket Stats', + 'STATISTICS': 'Statistics', 'LAST_ACTIVITY': 'Last Activity', 'MY_TICKETS': 'My Tickets', 'NEW_TICKETS': 'New Tickets', @@ -154,6 +154,15 @@ export default { 'VERIFY_SUCCESS': 'User verified', 'VERIFY_FAILED': 'Could not verify', 'CHECK_TICKET': 'Check Ticket', + 'STATISTICS': 'Statistics', + 'ACTIVITY': 'Activity', + + + 'CHART_CREATE_TICKET': 'Tickets created', + 'CHART_CLOSE': 'Tickets closed', + 'CHART_SIGNUP': 'Signups', + 'CHART_COMMENT': 'Replies', + 'CHART_ASSIGN': 'Assigned', //ACTIVITIES 'ACTIVITY_COMMENT': 'commented ticket', @@ -188,7 +197,7 @@ export default { 'TICKET_LIST_DESCRIPTION': 'Here you can find a list of all tickets you have sent to our support team.', 'TICKETS_DESCRIPTION': 'Send ticket through our support center and get response of your doubts, suggestions and issues.', 'ARTICLES_DESCRIPTION': 'Take a look to our articles about common issues, guides and documentation.', - 'ACCOUNT_DESCRIPTION': 'All your tickets are stored in your accounts\'s profile. Keep track off all your tickets you send to our staff team.', + 'ACCOUNT_DESCRIPTION': 'All your tickets are stored in your account\'s profile. Keep track of all your tickets you send to our staff team.', 'SUPPORT_CENTER_DESCRIPTION': 'Welcome to our support center. You can contact us through a tickets system. Your tickets will be answered by our staff.', 'CUSTOM_RESPONSES_DESCRIPTION': 'Custom responses are automated responses for common problems', 'MY_TICKETS_DESCRIPTION': 'Here you can view the tickets you are responsible for.', @@ -204,7 +213,7 @@ export default { 'ADD_ARTICLE_DESCRIPTION': 'Here you can add an article that will be available for every user. It will be added inside the category {category}.', 'LIST_ARTICLES_DESCRIPTION': 'This is a list of articles that includes information about our services.', 'ADD_TOPIC_DESCRIPTION': 'Here you can add a topic that works as a category for articles.', - 'DELETE_ARTICLE_DESCRIPTION': 'You\'re going to delete this article for ever.', + 'DELETE_ARTICLE_DESCRIPTION': 'You\'re going to delete this article forever.', 'STAFF_MEMBERS_DESCRIPTION': 'Here you can see who are your staff members.', 'ADD_STAFF_DESCRIPTION': 'Here you can add staff members to your teams.', 'EDIT_STAFF_DESCRIPTION': 'Here you can edit information about a staff member.', @@ -215,6 +224,7 @@ export default { 'SYSTEM_PREFERENCES_DESCRIPTION': 'Here you can edit the preferences of the system.', 'VERIFY_SUCCESS_DESCRIPTION': 'You user has been verified correctly. You can log in now.', 'VERIFY_FAILED_DESCRIPTION': 'The verification could not be done.', + 'STATISTICS_DESCRIPTION': 'Here you can view statistics related to tickets and signups.', //ERRORS 'EMAIL_OR_PASSWORD': 'Email or password invalid', diff --git a/client/src/reducers/session-reducer.js b/client/src/reducers/session-reducer.js index be4323cc..a51d7e67 100644 --- a/client/src/reducers/session-reducer.js +++ b/client/src/reducers/session-reducer.js @@ -44,7 +44,8 @@ class SessionReducer extends Reducer { logged: true, pending: false, failed: false, - staff: payload.data.staff + staff: payload.data.staff, + userId: payload.data.userId }); } @@ -75,7 +76,8 @@ class SessionReducer extends Reducer { initDone: true, logged: true, pending: false, - failed: false + failed: false, + userId: payload.data.userId }); } @@ -117,6 +119,7 @@ class SessionReducer extends Reducer { onSessionChecked(state) { let userData = sessionStore.getUserData(); + let userId = sessionStore.getSessionData().userId; return _.extend({}, state, { initDone: true, @@ -127,7 +130,8 @@ class SessionReducer extends Reducer { userProfilePic: userData.profilePic, userLevel: userData.level, userDepartments: userData.departments, - userTickets: userData.tickets + userTickets: userData.tickets, + userId: userId }); } diff --git a/server/controllers/article/get-all.php b/server/controllers/article/get-all.php index 9d90e152..40a49efc 100644 --- a/server/controllers/article/get-all.php +++ b/server/controllers/article/get-all.php @@ -7,7 +7,7 @@ class GetAllArticlesController extends Controller { public function validations() { return [ - 'permission' => 'user', + 'permission' => (Controller::isUserSystemEnabled()) ? 'user' : 'any', 'requestData' => [] ]; } diff --git a/server/controllers/system.php b/server/controllers/system.php index 0ffd8dde..b36fc8c6 100644 --- a/server/controllers/system.php +++ b/server/controllers/system.php @@ -9,13 +9,16 @@ require_once 'system/get-logs.php'; require_once 'system/get-mail-templates.php'; require_once 'system/edit-mail-template.php'; require_once 'system/recover-mail-template.php'; -require_once 'system/get-stats.php'; require_once 'system/disable-registration.php'; require_once 'system/enable-registration.php'; +require_once 'system/disable-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/get-stats.php'; require_once 'system/delete-all-users.php'; +require_once 'system/csv-import.php'; require_once 'system/backup-database.php'; require_once 'system/download.php'; @@ -41,5 +44,8 @@ $systemControllerGroup->addController(new GetAllKeyController); $systemControllerGroup->addController(new DeleteAllUsersController); $systemControllerGroup->addController(new BackupDatabaseController); $systemControllerGroup->addController(new DownloadController); +$systemControllerGroup->addController(new CSVImportController); +$systemControllerGroup->addController(new DisableUserSystemController); +$systemControllerGroup->addController(new EnabledUserSystemController); $systemControllerGroup->finalize(); \ No newline at end of file diff --git a/server/controllers/system/csv-import.php b/server/controllers/system/csv-import.php new file mode 100644 index 00000000..ea3093b8 --- /dev/null +++ b/server/controllers/system/csv-import.php @@ -0,0 +1,55 @@ + 'staff_3', + 'requestData' => [] + ]; + } + + public function handler() { + $fileUploader = $this->uploadFile(); + + if(!$fileUploader instanceof FileUploader) { + throw new Exception(ERRORS::INVALID_FILE); + } + + $file = fopen($fileUploader->getFullFilePath(),'r'); + $errors = []; + + while(!feof($file)) { + $userList = fgetcsv($file); + + Controller::setDataRequester(function ($key) use ($userList) { + switch ($key) { + case 'email': + return $userList[0]; + case 'password': + return $userList[1]; + case 'name': + return $userList[2]; + } + + return null; + }); + + $signupController = new SignUpController(true); + + try { + $signupController->validate(); + $signupController->handler(); + } catch (\Exception $exception) { + $errors[] = $exception->getMessage() . ' in email ' . $userList[0]; + } + } + + fclose($file); + + unlink($fileUploader->getFullFilePath()); + + Response::respondSuccess($errors); + } +} \ No newline at end of file diff --git a/server/controllers/system/disable-user-system.php b/server/controllers/system/disable-user-system.php new file mode 100644 index 00000000..fc1cc0a1 --- /dev/null +++ b/server/controllers/system/disable-user-system.php @@ -0,0 +1,58 @@ + 'staff_3', + 'requestData' => [] + ]; + } + + public function handler() { + $password = Controller::request('password'); + + if(!Hashing::verifyPassword($password, Controller::getLoggedUser()->password)) { + throw new Exception(ERRORS::INVALID_PASSWORD); + + } + + if(!Controller::isUserSystemEnabled()) { + throw new Exception(ERRORS::SYSTEM_USER_IS_ALREADY_DISABLED); + } + + $userSystemEnabled = Setting::getSetting('user-system-enabled'); + $userSystemEnabled->value = 0 ; + $userSystemEnabled->store(); + + $userList = User::getAll(); + + foreach($userList as $user) { + $ticketNumberList = []; + + foreach($user->sharedTicketList as $ticket) { + $ticket->authorEmail = $user->email; + $ticket->authorName = $user->name; + $ticket->author = null; + + $ticketNumberList[] = $ticket->ticketNumber; + $ticket->store(); + } + + $mailSender = new MailSender(); + + $mailSender->setTemplate(MailTemplate::USER_SYSTEM_DISABLED, [ + 'to' => $user->email, + 'name' => $user->name, + 'tickets' => json_encode($ticketNumberList) + ]); + + $mailSender->send(); + + $user->delete(); + } + + Response::respondSuccess(); + } +} \ No newline at end of file diff --git a/server/controllers/system/enabled-user-system.php b/server/controllers/system/enabled-user-system.php new file mode 100644 index 00000000..0139608a --- /dev/null +++ b/server/controllers/system/enabled-user-system.php @@ -0,0 +1,78 @@ + 'staff_3', + 'requestData' => [] + ]; + } + + public function handler() { + $password = Controller::request('password'); + + if(!Hashing::verifyPassword($password, Controller::getLoggedUser()->password)) { + throw new Exception(ERRORS::INVALID_PASSWORD); + + } + + if(Controller::isUserSystemEnabled()) { + throw new Exception(ERRORS::SYSTEM_USER_IS_ALREADY_ENABLED); + } + + $userSystemEnabled = Setting::getSetting('user-system-enabled'); + $userSystemEnabled->value = 1 ; + $userSystemEnabled->store(); + + $ticketList = Ticket::getAll(); + + foreach($ticketList as $ticket) { + + $userRow = User::getDataStore($ticket->authorEmail, 'email'); + + if($userRow->isNull()) { + $this->createUser($ticket->authorEmail,$ticket->authorName); + + } else { + $userRow->tickets = $userRow->tickets + 1; + $userRow->sharedTicketList->add($ticket); + $userRow->store(); + } + + $actualUserRow = User::getDataStore($ticket->authorEmail,'email'); + $ticket->author = $actualUserRow; + $ticket->authorName = null; + $ticket->authorEmail = null; + $ticket->store(); + } + + Response::respondSuccess(); + } + public function createUser($email,$name) { + $userInstance = new User(); + + $password = Hashing::generateRandomToken(); + + $userInstance->setProperties([ + 'name' => $name, + 'signupDate' => Date::getCurrentDate(), + 'tickets' => 1, + 'email' => $email, + 'password' => Hashing::hashPassword($password), + 'verificationToken' => null + ]); + + $userInstance->store(); + + $mailSender = new MailSender(); + $mailSender->setTemplate(MailTemplate::USER_SYSTEM_ENABLED, [ + 'to' => $email, + 'name' => $name, + 'password' => $password + ]); + $mailSender->send(); + + } +} \ No newline at end of file diff --git a/server/controllers/system/get-settings.php b/server/controllers/system/get-settings.php index 9cddb302..41e2299d 100644 --- a/server/controllers/system/get-settings.php +++ b/server/controllers/system/get-settings.php @@ -46,7 +46,8 @@ class GetSettingsController extends Controller { 'registration' => Setting::getSetting('registration')->getValue(), 'departments' => Department::getDepartmentNames(), 'supportedLanguages' => Language::getSupportedLanguages(), - 'allowedLanguages' => Language::getAllowedLanguages() + 'allowedLanguages' => Language::getAllowedLanguages(), + 'user-system-enabled' => Setting::getSetting('user-system-enabled')->getValue() ]; } diff --git a/server/controllers/system/init-settings.php b/server/controllers/system/init-settings.php index b5d7c6d3..4106797b 100644 --- a/server/controllers/system/init-settings.php +++ b/server/controllers/system/init-settings.php @@ -42,6 +42,7 @@ class InitSettingsController extends Controller { 'title' => 'Support Center', 'url' => 'http://www.opensupports.com/support', 'registration' => true, + 'user-system-enabled' => true, 'last-stat-day' => '20170101', //TODO: get current date 'ticket-gap' => Hashing::generateRandomPrime(100000, 999999), 'file-gap' => Hashing::generateRandomPrime(100000, 999999), diff --git a/server/controllers/ticket/comment.php b/server/controllers/ticket/comment.php index 6a043757..cd7713e1 100644 --- a/server/controllers/ticket/comment.php +++ b/server/controllers/ticket/comment.php @@ -9,7 +9,7 @@ class CommentController extends Controller { private $content; public function validations() { - return [ + $validations = [ 'permission' => 'user', 'requestData' => [ 'content' => [ @@ -22,13 +22,23 @@ class CommentController extends Controller { ] ] ]; + + if(!Controller::isUserSystemEnabled()) { + $validations['permission'] = 'any'; + $validations['requestData']['email'] = [ + 'validation' => DataValidator::email(), + 'error' => ERRORS::INVALID_EMAIL + ]; + } + + return $validations; } public function handler() { $session = Session::getInstance(); $this->requestData(); - if ($session->isLoggedWithId($this->ticket->author->id) || Controller::isStaffLogged()) { + if (!Controller::isUserSystemEnabled() || $session->isLoggedWithId($this->ticket->author->id) || Controller::isStaffLogged()) { $this->storeComment(); Log::createLog('COMMENT', $this->ticket->ticketNumber); @@ -41,23 +51,29 @@ class CommentController extends Controller { private function requestData() { $ticketNumber = Controller::request('ticketNumber'); - + $email = Controller::request('email'); $this->ticket = Ticket::getByTicketNumber($ticketNumber); $this->content = Controller::request('content'); + + if(!Controller::isUserSystemEnabled() && $this->ticket->authorEmail !== $email && !Controller::isStaffLogged()) { + throw new Exception(ERRORS::NO_PERMISSION); + } } private function storeComment() { + $fileUploader = $this->uploadFile(); + $comment = Ticketevent::getEvent(Ticketevent::COMMENT); $comment->setProperties(array( 'content' => $this->content, - 'file' => $this->uploadFile(), + 'file' => ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null, 'date' => Date::getCurrentDate() )); if(Controller::isStaffLogged()) { $this->ticket->unread = true; $comment->authorStaff = Controller::getLoggedUser(); - } else { + } else if(Controller::isUserSystemEnabled()) { $this->ticket->unreadStaff = true; $comment->authorUser = Controller::getLoggedUser(); } diff --git a/server/controllers/ticket/create.php b/server/controllers/ticket/create.php index fbcd9203..5b39ab47 100644 --- a/server/controllers/ticket/create.php +++ b/server/controllers/ticket/create.php @@ -10,9 +10,11 @@ class CreateController extends Controller { private $departmentId; private $language; private $ticketNumber; + private $email; + private $name; public function validations() { - return [ + $validations = [ 'permission' => 'user', 'requestData' => [ 'title' => [ @@ -33,6 +35,16 @@ class CreateController extends Controller { ] ] ]; + + if(!Controller::isUserSystemEnabled()) { + $validations['permission'] = 'any'; + $validations['requestData']['captcha'] = [ + 'validation' => DataValidator::captcha(), + 'error' => ERRORS::INVALID_CAPTCHA + ]; + } + + return $validations; } public function handler() { @@ -40,6 +52,8 @@ class CreateController extends Controller { $this->content = Controller::request('content'); $this->departmentId = Controller::request('departmentId'); $this->language = Controller::request('language'); + $this->email = Controller::request('email'); + $this->name = Controller::request('name'); $this->storeTicket(); @@ -53,6 +67,8 @@ class CreateController extends Controller { $department = Department::getDataStore($this->departmentId); $author = Controller::getLoggedUser(); + $fileUploader = $this->uploadFile(); + $ticket = new Ticket(); $ticket->setProperties(array( 'title' => $this->title, @@ -60,17 +76,22 @@ class CreateController extends Controller { 'language' => $this->language, 'author' => $author, 'department' => $department, - 'file' => $this->uploadFile(), + 'file' => ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null, 'date' => Date::getCurrentDate(), 'unread' => false, 'unreadStaff' => true, 'closed' => false, + 'authorName' => $this->name, + 'authorEmail' => $this->email )); - $author->sharedTicketList->add($ticket); - $author->tickets++; + if(Controller::isUserSystemEnabled()) { + $author->sharedTicketList->add($ticket); + $author->tickets++; + + $author->store(); + } - $author->store(); $ticket->store(); $this->ticketNumber = $ticket->ticketNumber; diff --git a/server/controllers/ticket/get.php b/server/controllers/ticket/get.php index 36ef3f8b..6e2a6758 100644 --- a/server/controllers/ticket/get.php +++ b/server/controllers/ticket/get.php @@ -8,7 +8,7 @@ class TicketGetController extends Controller { private $ticket; public function validations() { - return [ + $validations = [ 'permission' => 'user', 'requestData' => [ 'ticketNumber' => [ @@ -17,13 +17,38 @@ class TicketGetController extends Controller { ] ] ]; + + if(!Controller::isUserSystemEnabled() && !Controller::isStaffLogged()) { + $validations['permission'] = 'any'; + $validations['requestData']['email'] = [ + 'validation' => DataValidator::email(), + 'error' => ERRORS::INVALID_EMAIL + ]; + $validations['requestData']['captcha'] = [ + 'validation' => DataValidator::captcha(), + 'error' => ERRORS::INVALID_CAPTCHA + ]; + } + + return $validations; } public function handler() { + $email = Controller::request('email'); + $this->ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber')); + if(!Controller::isUserSystemEnabled() && !Controller::isStaffLogged()) { + if($this->ticket->authorEmail === $email) { + Response::respondSuccess($this->ticket->toArray()); + return; + } else { + throw new Exception(ERRORS::NO_PERMISSION); + } + } + if ($this->shouldDenyPermission()) { - Response::respondError(ERRORS::NO_PERMISSION); + throw new Exception(ERRORS::NO_PERMISSION); } else { Response::respondSuccess($this->ticket->toArray()); } @@ -32,7 +57,7 @@ class TicketGetController extends Controller { private function shouldDenyPermission() { $user = Controller::getLoggedUser(); - return (!Controller::isStaffLogged() && $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); } } \ No newline at end of file diff --git a/server/controllers/user/delete.php b/server/controllers/user/delete.php index 75cf7dfc..b17ed8a4 100644 --- a/server/controllers/user/delete.php +++ b/server/controllers/user/delete.php @@ -20,6 +20,10 @@ class DeleteUserController extends Controller { } public function handler() { + if(!Controller::isUserSystemEnabled()) { + throw new Exception(ERRORS::USER_SYSTEM_DISABLED); + } + $userId = Controller::request('userId'); $user = User::getDataStore($userId); diff --git a/server/controllers/user/get-user.php b/server/controllers/user/get-user.php index 8ea55c09..ae9b4f9e 100644 --- a/server/controllers/user/get-user.php +++ b/server/controllers/user/get-user.php @@ -18,6 +18,11 @@ class GetUserByIdController extends Controller { } public function handler() { + + if(!Controller::isUserSystemEnabled()) { + throw new Exception(ERRORS::USER_SYSTEM_DISABLED); + } + $userId = Controller::request('userId'); $user = User::getDataStore($userId); $staff = Controller::getLoggedUser(); diff --git a/server/controllers/user/get-users.php b/server/controllers/user/get-users.php index eadd6922..5d8daf28 100644 --- a/server/controllers/user/get-users.php +++ b/server/controllers/user/get-users.php @@ -21,6 +21,10 @@ class GetUsersController extends Controller { } public function handler() { + if(!Controller::isUserSystemEnabled()) { + throw new Exception(ERRORS::USER_SYSTEM_DISABLED); + } + $userList = $this->getUserList(); $userListArray = []; diff --git a/server/controllers/user/login.php b/server/controllers/user/login.php index 7eada075..ebd55c30 100644 --- a/server/controllers/user/login.php +++ b/server/controllers/user/login.php @@ -14,9 +14,12 @@ class LoginController extends Controller { } public function handler() { + if(!Controller::isUserSystemEnabled() && !Controller::request('staff')) { + throw new Exception(ERRORS::USER_SYSTEM_DISABLED); + } + if ($this->isAlreadyLoggedIn()) { - Response::respondError(ERRORS::SESSION_EXISTS); - return; + throw new Exception(ERRORS::SESSION_EXISTS); } if ($this->checkInputCredentials() || $this->checkRememberToken()) { diff --git a/server/controllers/user/recover-password.php b/server/controllers/user/recover-password.php index c5cec698..ac3ceef2 100644 --- a/server/controllers/user/recover-password.php +++ b/server/controllers/user/recover-password.php @@ -27,6 +27,10 @@ class RecoverPasswordController extends Controller { } public function handler() { + if(!Controller::isUserSystemEnabled()) { + throw new Exception(ERRORS::USER_SYSTEM_DISABLED); + } + $this->requestData(); $this->changePassword(); } diff --git a/server/controllers/user/send-recover-password.php b/server/controllers/user/send-recover-password.php index e92d1ae6..c59eebb1 100644 --- a/server/controllers/user/send-recover-password.php +++ b/server/controllers/user/send-recover-password.php @@ -21,6 +21,10 @@ class SendRecoverPasswordController extends Controller { } public function handler() { + if(!Controller::isUserSystemEnabled()) { + throw new Exception(ERRORS::USER_SYSTEM_DISABLED); + } + $email = Controller::request('email'); $this->user = User::getUser($email,'email'); diff --git a/server/controllers/user/signup.php b/server/controllers/user/signup.php index cee72f1b..4b9e4d89 100644 --- a/server/controllers/user/signup.php +++ b/server/controllers/user/signup.php @@ -10,9 +10,14 @@ class SignUpController extends Controller { private $userName; private $userPassword; private $verificationToken; + private $csvImported; + + public function __construct($csvImported = false) { + $this->csvImported = $csvImported; + } public function validations() { - return [ + $validations = [ 'permission' => 'any', 'requestData' => [ 'name' => [ @@ -26,35 +31,41 @@ class SignUpController extends Controller { 'password' => [ 'validation' => DataValidator::length(5, 200), 'error' => ERRORS::INVALID_PASSWORD - ], - 'captcha' => [ - 'validation' => DataValidator::captcha(), - 'error' => ERRORS::INVALID_CAPTCHA ] ] ]; + + if(!$this->csvImported) { + $validations['requestData']['captcha'] = [ + 'validation' => DataValidator::captcha(), + 'error' => ERRORS::INVALID_CAPTCHA + ]; + } + + return $validations; } public function handler() { + if(!Controller::isUserSystemEnabled()) { + throw new Exception(ERRORS::USER_SYSTEM_DISABLED); + } + $this->storeRequestData(); $apiKey = APIKey::getDataStore(Controller::request('apiKey'), 'token'); $existentUser = User::getUser($this->userEmail, 'email'); if (!$existentUser->isNull()) { - Response::respondError(ERRORS::USER_EXISTS); - return; + throw new Exception(ERRORS::USER_EXISTS); } $banRow = Ban::getDataStore($this->userEmail,'email'); if (!$banRow->isNull()) { - Response::respondError(ERRORS::ALREADY_BANNED); - return; + throw new Exception(ERRORS::ALREADY_BANNED); } - if (!Setting::getSetting('registration')->value && $apiKey->isNull() ) { - Response::respondError(ERRORS::NO_PERMISSION); - return; + if (!Setting::getSetting('registration')->value && $apiKey->isNull() && !$this->csvImported) { + throw new Exception(ERRORS::NO_PERMISSION); } $userId = $this->createNewUserAndRetrieveId(); diff --git a/server/controllers/user/verify.php b/server/controllers/user/verify.php index 616d3b92..aa2dd31d 100644 --- a/server/controllers/user/verify.php +++ b/server/controllers/user/verify.php @@ -17,6 +17,10 @@ class VerifyController extends Controller{ } public function handler() { + if(!Controller::isUserSystemEnabled()) { + throw new Exception(ERRORS::USER_SYSTEM_DISABLED); + } + $email = Controller::request('email'); $token = Controller::request('token'); diff --git a/server/data/ERRORS.php b/server/data/ERRORS.php index 5f47e71a..d75c7e2b 100644 --- a/server/data/ERRORS.php +++ b/server/data/ERRORS.php @@ -35,6 +35,9 @@ class ERRORS { const INVALID_TEMPLATE = 'INVALID_TEMPLATE'; const INVALID_SUBJECT = 'INVALID_SUBJECT'; const INVALID_BODY = 'INVALID_BODY'; + const USER_SYSTEM_DISABLED = 'USER_SYSTEM_DISABLED'; + const SYSTEM_USER_IS_ALREADY_DISABLED = 'SYSTEM_USER_IS_ALREADY_DISABLED'; + 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'; diff --git a/server/data/InitialMails.php b/server/data/InitialMails.php index 899d9af3..7be66192 100644 --- a/server/data/InitialMails.php +++ b/server/data/InitialMails.php @@ -53,6 +53,26 @@ class InitialMails { 'body' => file_get_contents('data/mail-templates/user-recovered-password-es.html') ] ], + 'USER_SYSTEM_DISABLED' => [ + 'en' => [ + 'subject' => 'Account has been deleted - OpenSupports', + 'body' => file_get_contents('data/mail-templates/user-system-disabled-en.html') + ], + 'es' => [ + 'subject' => 'cuanta borrada - OpenSupports', + 'body' => file_get_contents('data/mail-templates/user-system-disabled-es.html') + ] + ], + 'USER_SYSTEM_ENABLED' => [ + 'en' => [ + 'subject' => 'account has been created - OpenSupports', + 'body' => file_get_contents('data/mail-templates/user-system-enabled-en.html') + ], + 'es' => [ + 'subject' => 'se te ha creado una cuenta - OpenSupports', + 'body' => file_get_contents('data/mail-templates/user-system-enabled-es.html') + ] + ] ]; } } \ No newline at end of file diff --git a/server/data/mail-templates/user-system-disabled-en.html b/server/data/mail-templates/user-system-disabled-en.html new file mode 100644 index 00000000..d42819c1 --- /dev/null +++ b/server/data/mail-templates/user-system-disabled-en.html @@ -0,0 +1,3 @@ +
+ Hi {{name}} the system user has been deleted this is the list of your tickets {{tickets}} +
\ No newline at end of file diff --git a/server/data/mail-templates/user-system-disabled-es.html b/server/data/mail-templates/user-system-disabled-es.html new file mode 100644 index 00000000..fb4f3603 --- /dev/null +++ b/server/data/mail-templates/user-system-disabled-es.html @@ -0,0 +1,3 @@ +
+ hoola {{name}} the system user has been deleted this is the list of your tickets {{tickets}} +
\ No newline at end of file diff --git a/server/data/mail-templates/user-system-enabled-en.html b/server/data/mail-templates/user-system-enabled-en.html new file mode 100644 index 00000000..5b2bd906 --- /dev/null +++ b/server/data/mail-templates/user-system-enabled-en.html @@ -0,0 +1,4 @@ +
+ hi {{name}} the system user has been Created this is your new password {{password}} , you can change it if you want + maxi puto +
\ No newline at end of file diff --git a/server/data/mail-templates/user-system-enabled-es.html b/server/data/mail-templates/user-system-enabled-es.html new file mode 100644 index 00000000..a1081984 --- /dev/null +++ b/server/data/mail-templates/user-system-enabled-es.html @@ -0,0 +1,3 @@ +
+ hoola {{name}} el sistema de usuarios se ha creado este es tu contra {{password}} , puedes cambairla si quieres +
\ No newline at end of file diff --git a/server/index.php b/server/index.php index d8508ba3..041b96a4 100644 --- a/server/index.php +++ b/server/index.php @@ -23,6 +23,8 @@ include_once 'libs/FileManager.php'; include_once 'libs/FileDownloader.php'; include_once 'libs/FileUploader.php'; +Controller::init(); + // LOAD DATA spl_autoload_register(function ($class) { $classPath = "data/{$class}.php"; diff --git a/server/libs/Controller.php b/server/libs/Controller.php index b82bdb0f..88e6b997 100644 --- a/server/libs/Controller.php +++ b/server/libs/Controller.php @@ -3,6 +3,7 @@ require_once 'libs/Validator.php'; require_once 'models/Session.php'; abstract class Controller { + private static $dataRequester; /** * Instance-related stuff @@ -28,10 +29,20 @@ abstract class Controller { $validator->validate($this->validations()); } - public static function request($key) { - $app = self::getAppInstance(); + public static function init() { + self::$dataRequester = function ($key) { + $app = self::getAppInstance(); - return $app->request()->post($key); + return $app->request()->post($key); + }; + } + + public static function setDataRequester($dataRequester) { + self::$dataRequester = $dataRequester; + } + + public static function request($key) { + return call_user_func(self::$dataRequester, $key); } public static function getLoggedUser() { @@ -77,9 +88,13 @@ abstract class Controller { $fileQuantity->value++; $fileQuantity->store(); - return $fileUploader->getFileName(); + return $fileUploader; } else { throw new Exception(ERRORS::INVALID_FILE); } } + + public static function isUserSystemEnabled() { + return Setting::getSetting('user-system-enabled')->getValue(); + } } \ No newline at end of file diff --git a/server/models/MailTemplate.php b/server/models/MailTemplate.php index a2931061..0379693b 100644 --- a/server/models/MailTemplate.php +++ b/server/models/MailTemplate.php @@ -8,6 +8,8 @@ class MailTemplate extends DataStore { const USER_PASSWORD = 'USER_PASSWORD'; const PASSWORD_FORGOT = 'PASSWORD_FORGOT'; const PASSWORD_RECOVERED = 'PASSWORD_RECOVERED'; + const USER_SYSTEM_DISABLED = 'USER_SYSTEM_DISABLED'; + const USER_SYSTEM_ENABLED = 'USER_SYSTEM_ENABLED'; public static function getTemplate($type) { $globalLanguage = Setting::getSetting('language')->value; diff --git a/server/models/Ticket.php b/server/models/Ticket.php index 88351ea6..e1e3d9b1 100644 --- a/server/models/Ticket.php +++ b/server/models/Ticket.php @@ -20,7 +20,9 @@ class Ticket extends DataStore { 'owner', 'ownTicketeventList', 'unreadStaff', - 'language' + 'language', + 'authorEmail', + 'authorName' ); } @@ -79,7 +81,9 @@ class Ticket extends DataStore { 'priority' => $this->priority, 'author' => $this->authorToArray(), 'owner' => $this->ownerToArray(), - 'events' => $this->eventsToArray() + 'events' => $this->eventsToArray(), + 'authorEmail' => $this->authorEmail, + 'authorName' => $this->authorName ]; } diff --git a/tests/init.rb b/tests/init.rb index 1ecc936f..91e50a42 100644 --- a/tests/init.rb +++ b/tests/init.rb @@ -5,7 +5,6 @@ require 'uri' require 'mysql' require 'json' require 'mechanize' -require 'date' require './libs.rb' require './scripts.rb' @@ -59,3 +58,5 @@ require './system/add-api-key.rb' require './system/delete-api-key.rb' require './system/get-all-keys.rb' require './system/file-upload-download.rb' +require './system/csv-import.rb' +require './system/disable-user-system.rb' diff --git a/tests/system/csv-import.rb b/tests/system/csv-import.rb new file mode 100644 index 00000000..61ac34ba --- /dev/null +++ b/tests/system/csv-import.rb @@ -0,0 +1,28 @@ +describe'system/csv-import' do + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + + it 'should create user with csv-import' do + + file = File.new('../server/files/test.csv', 'w+') + file.puts('prueba1@hotmail.com, contrasena1,ma') + file.puts('prueba2@hotmail.com,contrasena2,max') + file.puts('prueba3@hotmail.com,contrasena3,maxi') + file.close + result= request('/system/csv-import', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + file: File.open( "../server/files/test.csv") + }) + + (result['status']).should.equal('success') + row = $database.getRow('user', 'prueba1@hotmail.com', 'email') + (row['name']).should.equal('ma') + + row = $database.getRow('user', 'prueba2@hotmail.com', 'email') + (row['name']).should.equal('max') + + row = $database.getRow('user', 'prueba3@hotmail.com', 'email') + (row['name']).should.equal('maxi') + end +end diff --git a/tests/system/disable-user-system.rb b/tests/system/disable-user-system.rb new file mode 100644 index 00000000..076fdb23 --- /dev/null +++ b/tests/system/disable-user-system.rb @@ -0,0 +1,89 @@ +describe'system/disable-user-system' do + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + + it 'should disable the user system' do + result = request('/system/disable-user-system', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + password:$staff[:password] + }) + + puts result['message'] + (result['status']).should.equal('success') + + row = $database.getRow('setting', 'user-system-enabled', 'name') + + (row['value']).should.equal('0') + row = $database.getRow('user', 1, 'id') + (row).should.equal(nil) + + numberOftickets= $database.query("SELECT * FROM ticket WHERE author_id IS NULL AND author_email IS NOT NULL AND author_name IS NOT NULL") + + (numberOftickets.num_rows).should.equal(35) + + request('/user/logout') + + result = request('/user/signup', { + :name => 'test name', + :email => 'steve@mail.com', + :password => 'customm' + }) + + (result['status']).should.equal('fail') + (result['message']).should.equal('USER_SYSTEM_DISABLED') + + result = request('/user/login', { + email: @loginEmail, + password: @loginPass + }) + + (result['status']).should.equal('fail') + (result['message']).should.equal('USER_SYSTEM_DISABLED') + end + + it 'should not disable the user system if it is already disabled 'do + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + + result = request('/system/disable-user-system', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + password:$staff[:password] + }) + + (result['status']).should.equal('fail') + (result['message']).should.equal('SYSTEM_USER_IS_ALREADY_DISABLED') + end + + it 'should enabled the user system' do + result = request('/system/enabled-user-system', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + password:$staff[:password] + }) + + puts result['message'] + (result['status']).should.equal('success') + + row = $database.getRow('setting', 'user-system-enabled', 'name') + (row['value']).should.equal('1') + + numberOftickets= $database.query("SELECT * FROM ticket WHERE author_email IS NULL AND author_name IS NULL AND author_id IS NOT NULL" ) + + (numberOftickets.num_rows).should.equal(35) + + end + + it 'should not enabled the user system' do + result = request('/system/enabled-user-system', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + password:$staff[:password] + }) + + (result['status']).should.equal('fail') + (result['message']).should.equal('SYSTEM_USER_IS_ALREADY_ENABLED') + + end +end diff --git a/tests/system/get-mail-templates.rb b/tests/system/get-mail-templates.rb index 8e067655..f6b4ab10 100644 --- a/tests/system/get-mail-templates.rb +++ b/tests/system/get-mail-templates.rb @@ -10,6 +10,6 @@ describe'system/get-mail-templates' do (result['status']).should.equal('success') - (result['data'].size).should.equal(10) + (result['data'].size).should.equal(14) end end