mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-30 01:05:18 +02:00
[DEV-162] Fix ticket comment issues (#1081)
* Fix bug in UI * Fix bug in backend * Fix some issues * Improve coding * Improve coding
This commit is contained in:
parent
f5e9b2602c
commit
402af565a9
@ -31,7 +31,9 @@ class TicketEvent extends React.Component {
|
|||||||
private: React.PropTypes.string,
|
private: React.PropTypes.string,
|
||||||
edited: React.PropTypes.bool,
|
edited: React.PropTypes.bool,
|
||||||
edit: React.PropTypes.bool,
|
edit: React.PropTypes.bool,
|
||||||
onToggleEdit: React.PropTypes.func
|
onToggleEdit: React.PropTypes.func,
|
||||||
|
isLastComment: React.PropTypes.bool,
|
||||||
|
isTicketClosed: React.PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -92,12 +94,7 @@ class TicketEvent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderComment() {
|
renderComment() {
|
||||||
const {
|
const { author, date, edit, file } = this.props;
|
||||||
author,
|
|
||||||
date,
|
|
||||||
edit,
|
|
||||||
file
|
|
||||||
} = this.props;
|
|
||||||
const customFields = (author && author.customfields) || [];
|
const customFields = (author && author.customfields) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -142,10 +139,13 @@ class TicketEvent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
|
const { content, author, userId, userStaff, isLastComment, isTicketClosed } = this.props;
|
||||||
|
const { id, staff } = author;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-event__comment-content ql-editor">
|
<div className="ticket-event__comment-content ql-editor">
|
||||||
<div dangerouslySetInnerHTML={{__html: this.props.content}}></div>
|
<div dangerouslySetInnerHTML={{__html: content}}></div>
|
||||||
{((this.props.author.id == this.props.userId && this.props.author.staff == this.props.userStaff) || this.props.userStaff) ? this.renderEditIcon() : null}
|
{(id == userId && staff == userStaff && isLastComment && !isTicketClosed) ? this.renderEditIcon() : null }
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -83,30 +83,46 @@ class TicketViewer extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { ticket, userStaff, userId, editable, allowAttachments, assignmentAllowed } = this.props;
|
const { ticket, userStaff, userId, editable, allowAttachments, assignmentAllowed } = this.props;
|
||||||
const showResponseField = (!ticket.closed && (editable || !assignmentAllowed));
|
const { editTitle, loading, edit, editId } = this.state;
|
||||||
|
const { closed, author, content, date, edited, file, events} = ticket;
|
||||||
|
const showResponseField = (!closed && (editable || !assignmentAllowed));
|
||||||
|
const lastCommentIndex = events.map(
|
||||||
|
(event, index) => {
|
||||||
|
return {...event, index}}
|
||||||
|
).filter(
|
||||||
|
(event) => event.type === "COMMENT"
|
||||||
|
).at(-1).index;
|
||||||
|
|
||||||
|
const eventsWithModifiedComments = events.map(
|
||||||
|
(event, index) => {
|
||||||
|
return {...event, isLastComment: index === lastCommentIndex && event.type === "COMMENT"};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-viewer">
|
<div className="ticket-viewer">
|
||||||
{this.state.editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
|
{editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
|
||||||
{editable ? this.renderEditableHeaders() : this.renderHeaders()}
|
{editable ? this.renderEditableHeaders() : this.renderHeaders()}
|
||||||
<div className="ticket-viewer__content">
|
<div className="ticket-viewer__content">
|
||||||
<TicketEvent
|
<TicketEvent
|
||||||
loading={this.state.loading}
|
loading={loading}
|
||||||
type="COMMENT"
|
type="COMMENT"
|
||||||
author={ticket.author}
|
isLastComment={!events.filter(event => event.type === "COMMENT").length}
|
||||||
content={userStaff ? MentionsParser.parse(ticket.content) : ticket.content}
|
author={author}
|
||||||
|
isTicketClosed={closed}
|
||||||
|
content={userStaff ? MentionsParser.parse(content) : content}
|
||||||
userStaff={userStaff}
|
userStaff={userStaff}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
date={ticket.date}
|
date={date}
|
||||||
onEdit={this.onEdit.bind(this,0)}
|
onEdit={this.onEdit.bind(this,0)}
|
||||||
edited={ticket.edited}
|
edited={edited}
|
||||||
file={ticket.file}
|
file={file}
|
||||||
edit={this.state.edit && this.state.editId == 0}
|
edit={edit && editId == 0}
|
||||||
onToggleEdit={this.onToggleEdit.bind(this, 0)}
|
onToggleEdit={this.onToggleEdit.bind(this, 0)}
|
||||||
allowAttachments={allowAttachments} />
|
allowAttachments={allowAttachments} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__comments">
|
<div className="ticket-viewer__comments">
|
||||||
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
|
{eventsWithModifiedComments && eventsWithModifiedComments.map(this.renderTicketEvent.bind(this, closed))}
|
||||||
</div>
|
</div>
|
||||||
{showResponseField ? this.renderResponseField() : this.renderReopenCloseButtons()}
|
{showResponseField ? this.renderResponseField() : this.renderReopenCloseButtons()}
|
||||||
</div>
|
</div>
|
||||||
@ -405,22 +421,26 @@ class TicketViewer extends React.Component {
|
|||||||
return <Button type='link' size="medium" onClick={() => this.setState({["edit"+option]: false})}>{i18n('CLOSE')}</Button>
|
return <Button type='link' size="medium" onClick={() => this.setState({["edit"+option]: false})}>{i18n('CLOSE')}</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTicketEvent(options, index) {
|
renderTicketEvent(isTicketClosed, ticketEventObject, index) {
|
||||||
const { userStaff, ticket, userId, allowAttachments } = this.props;
|
const { userStaff, ticket, userId, allowAttachments } = this.props;
|
||||||
|
const { edit, editId } = this.state;
|
||||||
|
const { content, author, id} = ticketEventObject;
|
||||||
|
|
||||||
if(userStaff && typeof options.content === 'string') {
|
if(userStaff && typeof content === 'string') {
|
||||||
options.content = MentionsParser.parse(options.content);
|
ticketEventObject.content = MentionsParser.parse(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TicketEvent
|
<TicketEvent
|
||||||
{...options}
|
{...ticketEventObject}
|
||||||
author={(!_.isEmpty(options.author)) ? options.author : ticket.author}
|
isLastComment={ticketEventObject.isLastComment}
|
||||||
|
author={(!_.isEmpty(author)) ? author : ticket.author}
|
||||||
userStaff={userStaff}
|
userStaff={userStaff}
|
||||||
|
isTicketClosed={isTicketClosed}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
onEdit={this.onEdit.bind(this, options.id)}
|
onEdit={this.onEdit.bind(this, id)}
|
||||||
edit={this.state.edit && this.state.editId == options.id}
|
edit={edit && editId == id}
|
||||||
onToggleEdit={this.onToggleEdit.bind(this, options.id)}
|
onToggleEdit={this.onToggleEdit.bind(this, id)}
|
||||||
key={index}
|
key={index}
|
||||||
allowAttachments={allowAttachments} />
|
allowAttachments={allowAttachments} />
|
||||||
);
|
);
|
||||||
@ -735,16 +755,16 @@ class TicketViewer extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onEdit(ticketeventid,{content}) {
|
onEdit(ticketeventid, {content}) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true
|
loading: true
|
||||||
});
|
});
|
||||||
const data = {};
|
const data = {};
|
||||||
|
|
||||||
if(ticketeventid){
|
if(ticketeventid) {
|
||||||
data.ticketEventId = ticketeventid
|
data.ticketEventId = ticketeventid;
|
||||||
}else{
|
} else {
|
||||||
data.ticketNumber = this.props.ticket.ticketNumber
|
data.ticketNumber = this.props.ticket.ticketNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
API.call({
|
API.call({
|
||||||
|
@ -50,21 +50,29 @@ class EditCommentController extends Controller {
|
|||||||
$user = Controller::getLoggedUser();
|
$user = Controller::getLoggedUser();
|
||||||
$newcontent = Controller::request('content', true);
|
$newcontent = Controller::request('content', true);
|
||||||
$ticketNumberLog = null;
|
$ticketNumberLog = null;
|
||||||
|
|
||||||
$ticketevent = Ticketevent::getTicketEvent(Controller::request('ticketEventId'));
|
$ticketevent = Ticketevent::getTicketEvent(Controller::request('ticketEventId'));
|
||||||
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
|
|
||||||
|
|
||||||
if(!Controller::isStaffLogged() && ($user->id !== $ticketevent->authorUserId && $user->id !== $ticket->authorId ) ){
|
if(!$ticketevent->isNull()) {
|
||||||
|
$ticket = Ticket::getDataStore($ticketevent->ticketId);
|
||||||
|
} else {
|
||||||
|
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Controller::isStaffLogged() && $user->id !== $ticketevent->authorUserId && $user->id !== $ticket->authorId) {
|
||||||
throw new RequestException(ERRORS::NO_PERMISSION);
|
throw new RequestException(ERRORS::NO_PERMISSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Controller::isStaffLogged()){
|
if(Controller::isStaffLogged() && !$user->canManageTicket($ticket)) {
|
||||||
if(!$ticketevent->isNull()){
|
throw new RequestException(ERRORS::NO_PERMISSION);
|
||||||
$ticket = $ticketevent->ticket;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(!$user->canManageTicket($ticket)) {
|
if(!$ticketevent->isNull()) {
|
||||||
throw new RequestException(ERRORS::NO_PERMISSION);
|
if($ticketevent->type !== "COMMENT" || $ticket->closed || $ticket->getLatestEventOfType("COMMENT")['id'] !== $ticketevent->id) {
|
||||||
|
throw new RequestException(ERRORS::INVALID_TICKET_EVENT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(sizeof($ticket->getEventsOfType("COMMENT"))) {
|
||||||
|
throw new RequestException(ERRORS::INVALID_TICKET_EVENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,4 +233,23 @@ class Ticket extends DataStore {
|
|||||||
public function isOwner($user) {
|
public function isOwner($user) {
|
||||||
return !$user->isNull() && $this->owner && $user->id == $this->owner->id && ($user instanceof Staff);
|
return !$user->isNull() && $this->owner && $user->id == $this->owner->id && ($user instanceof Staff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEventsOfType($type) {
|
||||||
|
$ticketEvents = $this->eventsToArray();
|
||||||
|
$filteredEventsList = [];
|
||||||
|
|
||||||
|
foreach($ticketEvents as $ticketEvent) {
|
||||||
|
if($ticketEvent['type'] == $type) {
|
||||||
|
array_push($filteredEventsList, $ticketEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filteredEventsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLatestEventOfType($type) {
|
||||||
|
$filteredEventsList = $this->getEventsOfType($type);
|
||||||
|
|
||||||
|
return end($filteredEventsList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user