diff --git a/client/src/app-components/ticket-event.js b/client/src/app-components/ticket-event.js index 06a241d4..375f6391 100644 --- a/client/src/app-components/ticket-event.js +++ b/client/src/app-components/ticket-event.js @@ -7,6 +7,11 @@ import API from 'lib-app/api-call'; import DateTransformer from 'lib-core/date-transformer'; import Icon from 'core-components/icon'; import Tooltip from 'core-components/tooltip'; +import TextEditor from 'core-components/text-editor'; +import Button from 'core-components/button'; +import SubmitButton from 'core-components/submit-button'; +import Form from 'core-components/form'; +import FormField from 'core-components/form-field'; class TicketEvent extends React.Component { static propTypes = { @@ -23,6 +28,13 @@ class TicketEvent extends React.Component { content: React.PropTypes.string, date: React.PropTypes.string, private: React.PropTypes.string, + edited: React.PropTypes.bool, + edit: React.PropTypes.bool, + onToggleEdit: React.PropTypes.func + }; + + state = { + content: this.props.content }; render() { @@ -95,12 +107,38 @@ class TicketEvent extends React.Component { {(this.props.private*1) ? this.renderPrivateBadge() : null}
{DateTransformer.transformToString(this.props.date)}
-
- {this.renderFileRow(this.props.file)} + {!this.props.edit ? this.renderContent() : this.renderEditField()} + {this.renderFooter(this.props.file)} ); } + renderContent() { + return ( +
+
+ {((this.props.author.id === this.props.userId) || (this.props.userStaff)) ? this.renderEditIcon() : null} +
+ ) + } + renderEditIcon() { + return ( +
+ +
+ ) + } + renderEditField() { + return ( +
{this.setState({content:form.content})}} onSubmit={this.props.onEdit}> + +
+ {i18n('SUBMIT')} + +
+ + ); + } renderAssignment() { let assignedTo = this.props.content; let authorName = this.props.author.name; @@ -189,8 +227,9 @@ class TicketEvent extends React.Component { ); } - renderFileRow(file) { + renderFooter(file) { let node = null; + let edited = null; if (file) { node = {this.getFileLink(file)} ; @@ -198,9 +237,18 @@ class TicketEvent extends React.Component { node = i18n('NO_ATTACHMENT'); } + if (this.props.edited && this.props.type === 'COMMENT') { + edited = i18n('COMMENT_EDITED'); + } + return ( -
- {node} +
+
+ {edited} +
+
+ {node} +
); } diff --git a/client/src/app-components/ticket-event.scss b/client/src/app-components/ticket-event.scss index 26ccbf89..c5b6e920 100644 --- a/client/src/app-components/ticket-event.scss +++ b/client/src/app-components/ticket-event.scss @@ -96,21 +96,47 @@ border-top: none; padding: 20px 10px; text-align: left; - + position:relative; + img { max-width:100%; } + &__edit { + position:absolute; + top: 3px; + right: 9px; + align-self: right; + color:white; + :hover { + color: grey; + cursor:pointer; + } + } } } + &__submit-edited-comment { + display:flex; + align-items: center; + justify-content: space-between; + padding: 8px; + } - &__file { + &__items { background-color: $very-light-grey; - cursor: pointer; - text-align: right; - padding: 5px 10px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px; font-size: 12px; } + &__file { + cursor: pointer; + text-align: right; + } + &__edited{ + font-style: italic; + } &__comment-badge-value { font-weight: normal; } diff --git a/client/src/app-components/ticket-viewer.js b/client/src/app-components/ticket-viewer.js index fd724874..09e1fbad 100644 --- a/client/src/app-components/ticket-viewer.js +++ b/client/src/app-components/ticket-viewer.js @@ -47,7 +47,8 @@ class TicketViewer extends React.Component { ticket: { author: {}, department: {}, - comments: [] + comments: [], + edited: null } }; @@ -55,7 +56,9 @@ class TicketViewer extends React.Component { loading: false, commentValue: TextEditor.createEmpty(), commentEdited: false, - commentPrivate: false + commentPrivate: false, + edit: false, + editId: 0 }; componentDidMount() { @@ -78,7 +81,21 @@ class TicketViewer extends React.Component {
{this.props.editable ? this.renderEditableHeaders() : this.renderHeaders()}
- +
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))} @@ -218,9 +235,18 @@ class TicketViewer extends React.Component { if (this.props.userStaff && typeof options.content === 'string') { options.content = MentionsParser.parse(options.content); } - return ( - + ); } @@ -452,6 +478,51 @@ class TicketViewer extends React.Component { } } + onToggleEdit(ticketEventId){ + this.setState({ + edit: !this.state.edit, + editId: ticketEventId + }) + } + + onEdit(ticketeventid,{content}) { + this.setState({ + loading: true + }); + const data = {}; + + if(ticketeventid){ + data.ticketeventId = ticketeventid + }else{ + data.ticketNumber = this.props.ticket.ticketNumber + } + + API.call({ + path: '/ticket/edit-comment', + data: _.extend( + data, + TextEditor.getContentFormData(content) + ) + }).then(this.onEditCommentSuccess.bind(this), this.onFailCommentFail.bind(this)); + } + + onEditCommentSuccess() { + this.setState({ + loading: false, + commentError: false, + commentEdited: false, + edit:false + }); + + this.onTicketModification(); + } + + onFailCommentFail() { + this.setState({ + loading: false, + commentError: true + }); + } onSubmit(formState) { this.setState({ loading: true diff --git a/client/src/app/Routes.js b/client/src/app/Routes.js index d2dfbcf5..0419c0db 100644 --- a/client/src/app/Routes.js +++ b/client/src/app/Routes.js @@ -35,7 +35,6 @@ import AdminPanelNewTickets from 'app/admin/panel/tickets/admin-panel-new-ticket import AdminPanelAllTickets from 'app/admin/panel/tickets/admin-panel-all-tickets'; import AdminPanelViewTicket from 'app/admin/panel/tickets/admin-panel-view-ticket'; import AdminPanelCustomResponses from 'app/admin/panel/tickets/admin-panel-custom-responses'; -import AdminPanelCustomTags from 'app/admin/panel/tickets/admin-panel-custom-tags'; import AdminPanelListUsers from 'app/admin/panel/users/admin-panel-list-users'; import AdminPanelViewUser from 'app/admin/panel/users/admin-panel-view-user'; @@ -52,6 +51,7 @@ import AdminPanelViewStaff from 'app/admin/panel/staff/admin-panel-view-staff'; import AdminPanelSystemPreferences from 'app/admin/panel/settings/admin-panel-system-preferences'; import AdminPanelAdvancedSettings from 'app/admin/panel/settings/admin-panel-advanced-settings'; import AdminPanelEmailSettings from 'app/admin/panel/settings/admin-panel-email-settings'; +import AdminPanelCustomTags from 'app/admin/panel/settings/admin-panel-custom-tags'; // INSTALLATION import InstallLayout from 'app/install/install-layout'; @@ -115,7 +115,6 @@ export default ( - @@ -144,6 +143,7 @@ export default ( + diff --git a/client/src/app/admin/panel/admin-panel-menu.js b/client/src/app/admin/panel/admin-panel-menu.js index aaf8d634..da34ccb7 100644 --- a/client/src/app/admin/panel/admin-panel-menu.js +++ b/client/src/app/admin/panel/admin-panel-menu.js @@ -135,11 +135,6 @@ class AdminPanelMenu extends React.Component { name: i18n('CUSTOM_RESPONSES'), path: '/admin/panel/tickets/custom-responses', level: 2 - }, - { - name: i18n('CUSTOM_TAGS'), - path: '/admin/panel/tickets/custom-tags', - level: 1 } ]) }, @@ -219,6 +214,11 @@ class AdminPanelMenu extends React.Component { name: i18n('EMAIL_SETTINGS'), path: '/admin/panel/settings/email-settings', level: 3 + }, + { + name: i18n('CUSTOM_TAGS'), + path: '/admin/panel/settings/custom-tags', + level: 3 } ]) } diff --git a/client/src/app/admin/panel/settings/admin-panel-custom-tags.js b/client/src/app/admin/panel/settings/admin-panel-custom-tags.js new file mode 100644 index 00000000..c74c5bb3 --- /dev/null +++ b/client/src/app/admin/panel/settings/admin-panel-custom-tags.js @@ -0,0 +1,102 @@ +import React from 'react'; +import {connect} from 'react-redux'; + +import AdminPanelCustomTagsModal from 'app/admin/panel/tickets/admin-panel-custom-tags-modal'; + +import i18n from 'lib-app/i18n'; +import API from 'lib-app/api-call'; +import ConfigActions from 'actions/config-actions'; + +import AreYouSure from 'app-components/are-you-sure'; +import ModalContainer from 'app-components/modal-container'; + +import Icon from 'core-components/icon'; +import Button from 'core-components/button'; +import Header from 'core-components/header'; +import Tag from 'core-components/tag'; + +class AdminPanelCustomTags extends React.Component { + static propTypes = { + tags: React.PropTypes.arrayOf( + React.PropTypes.shape({ + name: React.PropTypes.string, + color: React.PropTypes.string, + id: React.PropTypes.number + }) + ), + }; + + componentDidMount() { + this.retrieveCustomTags(); + } + + render() { + return ( +
+
+ {this.renderContent()} +
+ ); + } + + renderContent() { + return ( +
+
+ +
+
+ {this.props.tags.map(this.renderTag.bind(this))} +
+
+ ); + } + + renderTag(tag, index) { + return ( +
+ +
+ ) + } + + openTagModal() { + ModalContainer.openModal( + + ); + } + + openEditTagModal(tagId,tagName,tagColor, event) { + ModalContainer.openModal( + + ); + } + + onDeleteClick(tagId, event) { + event.preventDefault(); + AreYouSure.openModal(i18n('WILL_DELETE_CUSTOM_RESPONSE'), this.deleteCustomTag.bind(this, tagId)); + } + + deleteCustomTag(tagId) { + API.call({ + path: '/ticket/delete-tag', + data: { + tagId, + } + }).then(() => { + this.retrieveCustomTags() + }); + } + + retrieveCustomTags() { + this.props.dispatch(ConfigActions.updateData()); + } +} + +export default connect((store) => { + return { + tags: store.config['tags'] + }; +})(AdminPanelCustomTags); diff --git a/client/src/app/admin/panel/settings/admin-panel-custom-tags.scss b/client/src/app/admin/panel/settings/admin-panel-custom-tags.scss new file mode 100644 index 00000000..d8d8b51c --- /dev/null +++ b/client/src/app/admin/panel/settings/admin-panel-custom-tags.scss @@ -0,0 +1,18 @@ +.admin-panel-custom-tags { + + &__content { + text-align: left; + } + + &__add-button-icon{ + margin-left: 5px; + } + + &__tag-list{ + margin-top: 15px; + } + + &__tag-container{ + margin-top:5px ; + } +} diff --git a/client/src/app/admin/panel/tickets/admin-panel-custom-tags-modal.js b/client/src/app/admin/panel/tickets/admin-panel-custom-tags-modal.js index c3398729..9db3e6ed 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-custom-tags-modal.js +++ b/client/src/app/admin/panel/tickets/admin-panel-custom-tags-modal.js @@ -26,13 +26,45 @@ class AdminPanelCustomTagsModal extends React.Component { }; render() { + return ( + this.props.createTag ? this.renderCreateTagContent() : this.renderEditTagContent() + ); + } + + renderEditTagContent() { + return ( +
+
+
this.setState({errors})} + loading={this.state.loading}> + + +
+ + {i18n('SAVE')} + + +
+ +
+ ); + } + + renderCreateTagContent() { return (
this.setState({errors})} loading={this.state.loading}> @@ -57,7 +89,40 @@ class AdminPanelCustomTagsModal extends React.Component { }); } - onSubmitTag(form) { + onSubmitEditTag(form) { + this.setState({ + loading: true + }); + + API.call({ + path: '/ticket/edit-tag', + data: { + tagId: this.props.id, + name: form.name, + color: form.color, + } + }).then(() => { + this.context.closeModal(); + this.setState({ + loading: false, + errors: {} + }); + if(this.props.onTagChange) { + this.props.onTagChange(); + } + }).catch((result) => { + + this.setState({ + loading: false, + errors: { + 'name': result.message + } + }); + + }); + } + + onSubmitNewTag(form) { this.setState({ loading: true }); diff --git a/client/src/app/admin/panel/tickets/admin-panel-custom-tags.js b/client/src/app/admin/panel/tickets/admin-panel-custom-tags.js index d72f1e8b..79676a70 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-custom-tags.js +++ b/client/src/app/admin/panel/tickets/admin-panel-custom-tags.js @@ -55,7 +55,7 @@ class AdminPanelCustomTags extends React.Component { } renderTag(tag, index) { - return( + return (
diff --git a/client/src/core-components/form.js b/client/src/core-components/form.js index ea422d54..d6de43af 100644 --- a/client/src/core-components/form.js +++ b/client/src/core-components/form.js @@ -15,7 +15,8 @@ class Form extends React.Component { onValidateErrors: React.PropTypes.func, onChange: React.PropTypes.func, values: React.PropTypes.object, - onSubmit: React.PropTypes.func + onSubmit: React.PropTypes.func, + defaultValues: React.PropTypes.object }; static childContextTypes = { @@ -24,9 +25,8 @@ class Form extends React.Component { constructor(props) { super(props); - this.state = { - form: {}, + form: props.defaultValues || {}, validations: {}, errors: {} }; diff --git a/client/src/core-components/tag.js b/client/src/core-components/tag.js index d9518978..573aafff 100644 --- a/client/src/core-components/tag.js +++ b/client/src/core-components/tag.js @@ -16,11 +16,21 @@ class Tag extends React.Component { return (
event.stopPropagation()} > {this.props.name} - {this.props.showDeleteButton ? this.renderRemoveButton() : null} + + {this.props.showEditButton ? this.renderEditButton() : null} + {this.props.showDeleteButton ? this.renderRemoveButton() : null} +
); } + renderEditButton() { + return ( + + + + ); + } renderRemoveButton() { return ( diff --git a/client/src/core-components/tag.scss b/client/src/core-components/tag.scss index 4075d6b6..e8142431 100644 --- a/client/src/core-components/tag.scss +++ b/client/src/core-components/tag.scss @@ -5,6 +5,7 @@ display: inline-block; border-radius: 3px; margin-left: 5px; + margin-top: 3px; padding: 3px; font-size: 13px; cursor: default; @@ -17,6 +18,12 @@ color: $light-grey; } } + &__edit { + cursor: pointer; + &:hover { + color: $light-grey; + } + } &_small { font-size: 11px; diff --git a/client/src/core-components/text-editor.js b/client/src/core-components/text-editor.js index 3967ef8e..f3a1a733 100644 --- a/client/src/core-components/text-editor.js +++ b/client/src/core-components/text-editor.js @@ -45,7 +45,7 @@ class TextEditor extends React.Component { } state = { - value: '', + value: this.props.value, focused: false }; diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 70f6e6d6..6e6781de 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -106,6 +106,7 @@ export default { 'LAST_EDITED_IN': 'Last edited in {date}', 'EDIT': 'Edit', 'ADD_CUSTOM_TAG': 'Add custom tag', + 'EDIT_CUSTOM_TAG': 'Edit custom tag', 'NO_RESULTS': 'No results', 'DELETE_AND_BAN': 'Delete and ban', 'STAFF_LEVEL': 'Staff Level', @@ -209,6 +210,7 @@ export default { 'OPTIONS': 'Options', 'FIELD_DESCRIPTION': 'Field description (Optional)', 'DESCRIPTION_ADD_CUSTOM_TAG': 'here you can add a new custom tag', + 'DESCRIPTION_EDIT_CUSTOM_TAG': 'here you can edit a custom tag', 'CUSTOM_FIELDS': 'Custom fields', 'CHART_CREATE_TICKET': 'Tickets created', @@ -396,6 +398,7 @@ export default { 'SERVER_CREDENTIALS_WORKING': 'Server credentials are working correctly', 'DELETE_CUSTOM_FIELD_SURE': 'Some users may be using this field. Are you sure you want to delete it?', + 'COMMENT_EDITED': '(comment edited)', 'LAST_7_DAYS': 'Last 7 days', 'LAST_30_DAYS': 'Last 30 days', 'LAST_90_DAYS': 'Last 90 days', diff --git a/server/controllers/staff/delete.php b/server/controllers/staff/delete.php index 70ff34fe..fd51f58b 100755 --- a/server/controllers/staff/delete.php +++ b/server/controllers/staff/delete.php @@ -52,18 +52,18 @@ class DeleteStaffController extends Controller { foreach($staff->sharedTicketList as $ticket) { $ticket->owner = null; - $ticket->true = true; + $ticket->unreadStaff = true; $ticket->store(); } - + foreach($staff->sharedDepartmentList as $department) { $department->owners--; $department->store(); } - + RedBean::exec('DELETE FROM log WHERE author_staff_id = ?', [$staffId]); $staff->delete(); Response::respondSuccess(); } - -} \ No newline at end of file + +} diff --git a/server/controllers/system/email-polling.php b/server/controllers/system/email-polling.php index e9508da1..f4ae3c0c 100755 --- a/server/controllers/system/email-polling.php +++ b/server/controllers/system/email-polling.php @@ -30,7 +30,7 @@ class EmailPollingController extends Controller { throw new RequestException(ERRORS::INVALID_TOKEN); if(Controller::isUserSystemEnabled()) - throw new RequestException(ERRORS::USER_SYSTEM); + throw new RequestException(ERRORS::USER_SYSTEM_ENABLED); $this->mailbox = new \PhpImap\Mailbox( Setting::getSetting('imap-host')->getValue(), diff --git a/server/controllers/ticket.php b/server/controllers/ticket.php index 948f3221..5afbf8d5 100755 --- a/server/controllers/ticket.php +++ b/server/controllers/ticket.php @@ -3,6 +3,7 @@ $ticketControllers = new ControllerGroup(); $ticketControllers->setGroupPath('/ticket'); $ticketControllers->addController(new CreateController); +$ticketControllers->addController(new EditCommentController); $ticketControllers->addController(new CommentController); $ticketControllers->addController(new TicketGetController); $ticketControllers->addController(new CheckTicketController); diff --git a/server/controllers/ticket/comment.php b/server/controllers/ticket/comment.php index c446b809..1780a44e 100755 --- a/server/controllers/ticket/comment.php +++ b/server/controllers/ticket/comment.php @@ -95,7 +95,7 @@ class CommentController extends Controller { 'staff' => true ]); } else if($isOwner) { - $this->sendMail($ticketAuthor); + !Controller::request('private') ? $this->sendMail($ticketAuthor) : null; } Log::createLog('COMMENT', $this->ticket->ticketNumber); @@ -151,12 +151,12 @@ class CommentController extends Controller { } $mailSender->setTemplate(MailTemplate::TICKET_RESPONDED, [ - 'to' => $email, - 'name' => $name, - 'title' => $this->ticket->title, - 'ticketNumber' => $this->ticket->ticketNumber, - 'content' => $this->content, - 'url' => $url + 'to' => $email, + 'name' => $name, + 'title' => $this->ticket->title, + 'ticketNumber' => $this->ticket->ticketNumber, + 'content' => $this->content, + 'url' => $url ]); $mailSender->send(); diff --git a/server/controllers/ticket/create-tag.php b/server/controllers/ticket/create-tag.php index dd48ba5d..b969522a 100644 --- a/server/controllers/ticket/create-tag.php +++ b/server/controllers/ticket/create-tag.php @@ -12,7 +12,7 @@ DataValidator::with('CustomValidations', true); * * @apiDescription This path creates a new tag. * - * @apiPermission staff1 + * @apiPermission staff3 * * @apiParam {Number} name The new name of the tag. * @apiParam {String} color The new color of the tag. @@ -31,7 +31,7 @@ class CreateTagController extends Controller { public function validations() { return [ - 'permission' => 'staff_1', + 'permission' => 'staff_3', 'requestData' => [ 'name' => [ 'validation' => DataValidator::length(2, 100), diff --git a/server/controllers/ticket/delete-tag.php b/server/controllers/ticket/delete-tag.php index 78213d14..3d7f0ec3 100644 --- a/server/controllers/ticket/delete-tag.php +++ b/server/controllers/ticket/delete-tag.php @@ -12,7 +12,7 @@ DataValidator::with('CustomValidations', true); * * @apiDescription This path delete a tag. * - * @apiPermission staff1 + * @apiPermission staff3 * * @apiParam {Number} tagId The id of the tag. * @@ -29,7 +29,7 @@ class DeleteTagController extends Controller { public function validations() { return [ - 'permission' => 'staff_1', + 'permission' => 'staff_3', 'requestData' => [ 'tagId' => [ 'validation' => DataValidator::dataStoreId('tag'), diff --git a/server/controllers/ticket/delete.php b/server/controllers/ticket/delete.php index 7e447f99..1f8b094a 100644 --- a/server/controllers/ticket/delete.php +++ b/server/controllers/ticket/delete.php @@ -48,7 +48,7 @@ class DeleteController extends Controller { throw new RequestException(ERRORS::NO_PERMISSION); } - if(Controller::isStaffLogged() && $user->level < 3) { + if(Controller::isStaffLogged() && $user->level < 3 && ($user->email !== $ticketAuthor['email'])) { throw new RequestException(ERRORS::NO_PERMISSION); } diff --git a/server/controllers/ticket/edit-comment.php b/server/controllers/ticket/edit-comment.php new file mode 100644 index 00000000..5184192a --- /dev/null +++ b/server/controllers/ticket/edit-comment.php @@ -0,0 +1,68 @@ + 'user', + 'requestData' => [ + 'content' => [ + 'validation' => DataValidator::length(10, 5000), + 'error' => ERRORS::INVALID_CONTENT + ] + ] + ]; + } + + public function handler() { + $user = Controller::getLoggedUser(); + $newcontent = Controller::request('content'); + + $ticketevent = Ticketevent::getTicketEvent(Controller::request('ticketEventId')); + $ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber')); + + if(!Controller::isStaffLogged() && ($user->id !== $ticketevent->authorUserId && $user->id !== $ticket->authorId )){ + throw new RequestException(ERRORS::NO_PERMISSION); + } + + if(!$ticketevent->isNull()){ + $ticketevent->content = $newcontent; + $ticketevent->editedContent = true; + $ticketevent->store(); + }else{ + $ticket->content = $newcontent; + $ticket->editedContent = true; + $ticket->store(); + } + + + Response::respondSuccess(); + } +} diff --git a/server/controllers/ticket/edit-tag.php b/server/controllers/ticket/edit-tag.php index 2fe5e984..bf6b1826 100644 --- a/server/controllers/ticket/edit-tag.php +++ b/server/controllers/ticket/edit-tag.php @@ -12,7 +12,7 @@ DataValidator::with('CustomValidations', true); * * @apiDescription This path edit tags. * - * @apiPermission staff1 + * @apiPermission staff3 * * @apiParam {Number} tagId The id of the tag. * @apiParam {Number} name The new name of the tag. @@ -32,7 +32,7 @@ class EditTagController extends Controller { public function validations() { return [ - 'permission' => 'staff_1', + 'permission' => 'staff_3', 'requestData' => [ 'tagId' => [ 'validation' => DataValidator::dataStoreId('tag'), diff --git a/server/controllers/ticket/get-tags.php b/server/controllers/ticket/get-tags.php index ea0962ab..713eba48 100644 --- a/server/controllers/ticket/get-tags.php +++ b/server/controllers/ticket/get-tags.php @@ -12,7 +12,7 @@ DataValidator::with('CustomValidations', true); * * @apiDescription This path returns all the tags. * - * @apiPermission staff1 + * @apiPermission staff3 * * @apiUse NO_PERMISSION * @@ -26,14 +26,14 @@ class GetTagsController extends Controller { public function validations() { return [ - 'permission' => 'staff_1', + 'permission' => 'staff_3', 'requestData' => [] ]; } public function handler() { $tags = Tag::getAll(); - + Response::respondSuccess($tags->toArray()); } } diff --git a/server/models/Ticket.php b/server/models/Ticket.php index 6e7c3148..93fcd13d 100755 --- a/server/models/Ticket.php +++ b/server/models/Ticket.php @@ -50,7 +50,8 @@ class Ticket extends DataStore { 'language', 'authorEmail', 'authorName', - 'sharedTagList' + 'sharedTagList', + 'editedContent' ); } @@ -130,7 +131,8 @@ class Ticket extends DataStore { 'author' => $this->authorToArray(), 'owner' => $this->ownerToArray(), 'events' => $minimized ? [] : $this->eventsToArray(), - 'tags' => $this->sharedTagList->toArray(true) + 'tags' => $this->sharedTagList->toArray(true), + 'edited' => $this->editedContent ]; } @@ -181,6 +183,8 @@ class Ticket extends DataStore { 'date'=> $ticketEvent->date, 'file'=> $ticketEvent->file, 'private'=> $ticketEvent->private, + 'edited' => $ticketEvent->editedContent, + 'id' => $ticketEvent->id ]; $author = $ticketEvent->getAuthor(); diff --git a/server/models/Ticketevent.php b/server/models/Ticketevent.php index e62f7152..c2880455 100755 --- a/server/models/Ticketevent.php +++ b/server/models/Ticketevent.php @@ -60,7 +60,8 @@ class Ticketevent extends DataStore { 'authorUser', 'authorStaff', 'date', - 'private' + 'private', + 'editedContent' ]; } @@ -75,6 +76,10 @@ class Ticketevent extends DataStore { return new NullDataStore(); } + + public static function getTicketEvent($value, $property = 'id') { + return parent::getDataStore($value, $property); + } public function toArray() { $user = ($this->authorStaff) ? $this->authorStaff : $this->authorUser; @@ -87,7 +92,8 @@ class Ticketevent extends DataStore { 'staff' => $user instanceOf Staff, 'id' => $user ? $user->id : null, 'customfields' => $user->xownCustomfieldvalueList ? $user->xownCustomfieldvalueList->toArray() : [], - ] + ], + 'edited' => $this->editedContent ]; } } diff --git a/tests/init.rb b/tests/init.rb index 2002848b..5274d26f 100644 --- a/tests/init.rb +++ b/tests/init.rb @@ -68,5 +68,6 @@ require './ticket/get-tags.rb' require './ticket/delete-tag.rb' require './ticket/add-tag.rb' require './ticket/delete-tag.rb' +require './ticket/edit-comment.rb' require './system/disable-user-system.rb' # require './system/get-stats.rb' diff --git a/tests/scripts.rb b/tests/scripts.rb index 309d6bba..15b58d95 100644 --- a/tests/scripts.rb +++ b/tests/scripts.rb @@ -67,10 +67,10 @@ class Scripts request('/user/logout') end - def self.createTicket(title = 'Winter is coming') + def self.createTicket(title = 'Winter is coming',content = 'The north remembers') result = request('/ticket/create', { title: title, - content: 'The north remembers', + content: content, departmentId: 1, language: 'en', csrf_userid: $csrf_userid, @@ -115,4 +115,19 @@ class Scripts color: color }) end + def self.assignTicket(ticketnumber) + request('/staff/assign-ticket', { + ticketNumber: ticketnumber, + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) + end + def self.commentTicket(ticketnumber,content) + request('/ticket/comment', { + content: content, + ticketNumber: ticketnumber, + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) + end end diff --git a/tests/system/disable-user-system.rb b/tests/system/disable-user-system.rb index 703a71b4..3a0013ba 100644 --- a/tests/system/disable-user-system.rb +++ b/tests/system/disable-user-system.rb @@ -19,7 +19,7 @@ describe'system/disable-user-system' do numberOftickets= $database.query("SELECT * FROM ticket WHERE author_id IS NULL AND author_email IS NOT NULL AND author_name IS NOT NULL") - (numberOftickets.num_rows).should.equal(40) + (numberOftickets.num_rows).should.equal(41) request('/user/logout') @@ -127,7 +127,7 @@ describe'system/disable-user-system' do numberOftickets= $database.query("SELECT * FROM ticket WHERE author_email IS NULL AND author_name IS NULL AND author_id IS NOT NULL" ) - (numberOftickets.num_rows).should.equal(41) + (numberOftickets.num_rows).should.equal(42) end diff --git a/tests/ticket/edit-comment.rb b/tests/ticket/edit-comment.rb new file mode 100644 index 00000000..585f03ca --- /dev/null +++ b/tests/ticket/edit-comment.rb @@ -0,0 +1,97 @@ +describe '/ticket/edit-comment' do + + request('/user/logout') + Scripts.login(); + Scripts.createTicket('ticket made by an user','content of the ticket made by an user') + ticket = $database.getRow('ticket', 'ticket made by an user', 'title') + Scripts.commentTicket(ticket['ticket_number'],'com ment of a user') + + it 'should change content of the ticket if the author user tries it' do + result = request('/ticket/edit-comment', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + content: 'content edited by the user', + ticketNumber: ticket['ticket_number'] + }) + + ticket = $database.getRow('ticket', 'ticket made by an user', 'title') + + (result['status']).should.equal('success') + (ticket['content']).should.equal('content edited by the user') + end + + it 'should change the content of a comment if the user is the author' do + + ticketevent = $database.getRow('ticketevent', 'com ment of a user', 'content') + + result = request('/ticket/edit-comment', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + content: 'comment edited by the user', + ticketEventId: ticketevent['id'] + }) + + ticketevent = $database.getRow('ticketevent', 'comment edited by the user', 'content') + + (result['status']).should.equal('success') + (ticketevent['content']).should.equal('comment edited by the user') + end + + it 'should change the content of a comment and the content of the ticket if the admin is logged' do + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + ticketevent = $database.getRow('ticketevent', 'comment edited by the user', 'content') + + result = request('/ticket/edit-comment', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + content: 'comment edited by a staff', + ticketEventId: ticketevent['id'] + }) + + ticketevent = $database.getRow('ticketevent', 'comment edited by a staff', 'content') + + (result['status']).should.equal('success') + (ticketevent['content']).should.equal('comment edited by a staff') + + result = request('/ticket/edit-comment', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + content: 'content edited by a staff', + ticketNumber: ticket['ticket_number'] + }) + + ticket = $database.getRow('ticket', ticket['ticket_number'], 'ticket_number') + + (result['status']).should.equal('success') + (ticket['content']).should.equal('content edited by a staff') + + request('/user/logout') + end + + + it 'should not change the content of a comment if the user is not the author' do + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + + ticket = $database.getRow('ticket', 'ticket made by an user', 'title') + + Scripts.assignTicket(ticket['ticket_number']) + Scripts.commentTicket(ticket['ticket_number'],'this is a new comment of a staff member') + + ticketevent = $database.getRow('ticketevent', 'this is a new comment of a staff member', 'content') + + request('/user/logout') + Scripts.login(); + + result = request('/ticket/edit-comment', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + content: 'comment edited by an user', + ticketEventId: ticketevent['id'] + }) + (result['status']).should.equal('fail') + (result['message']).should.equal('NO_PERMISSION') + end + +end