From 008cfeea69313a8941419db3ab3c8aba240b3f48 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 6 Mar 2018 06:31:53 +0100 Subject: [PATCH] Fixed issue #1857 - Missing To and Cc field when forwarding an article of a customer without an email address. --- .../article_action/email_reply.coffee | 118 ++++++++- .../article_action/facebook_reply.coffee | 24 ++ .../ticket_zoom/article_action/note.coffee | 23 ++ .../article_action/phone_reply.coffee | 19 ++ .../article_action/telegram.coffee | 34 +++ .../article_action/twitter_reply.coffee | 66 +++++ .../ticket_zoom/article_new.coffee | 232 ++---------------- 7 files changed, 307 insertions(+), 209 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/ticket_zoom/article_action/note.coffee create mode 100644 app/assets/javascripts/app/controllers/ticket_zoom/article_action/phone_reply.coffee diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee index f233d2bb2..4845d1e98 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee @@ -1,15 +1,18 @@ class EmailReply extends App.Controller @action: (actions, ticket, article, ui) -> - return actions if ui.permissionCheck('ticket.customer') - + return actions if !ui.permissionCheck('ticket.agent') group = ticket.group - if group.email_address_id && (article.type.name is 'email' || article.type.name is 'web') + return actions if !group.email_address_id + + if article.type.name is 'email' || article.type.name is 'web' actions.push { name: 'reply' type: 'emailReply' icon: 'reply' href: '#' } + + # check if reply all need to be shown recipients = [] if article.sender.name is 'Customer' if article.from @@ -51,6 +54,7 @@ class EmailReply extends App.Controller href: '#' } + # always show forward actions.push { name: 'forward' type: 'emailForward' @@ -113,6 +117,12 @@ class EmailReply extends App.Controller # empty form articleNew = App.Utils.getRecipientArticle(ticket, article, article_created_by, type, email_addresses, all) + if ui.Config.get('ui_ticket_zoom_article_email_subject') + if _.isEmpty(article.subject) + articleNew.subject = ticket.title + else + articleNew.subject = article.subject + # get current body body = ui.el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || '' @@ -172,6 +182,12 @@ class EmailReply extends App.Controller articleNew = {} articleNew.body = body + if ui.Config.get('ui_ticket_zoom_article_email_subject') + if _.isEmpty(article.subject) + articleNew.subject = "FW: #{ticket.title}" + else + articleNew.subject = "FW: #{article.subject}" + type = App.TicketArticleType.findByAttribute(name:'email') App.Event.trigger('ui::ticket::setArticleType', { @@ -200,4 +216,100 @@ class EmailReply extends App.Controller true + @articleTypes: (articleTypes, ticket, ui) -> + return articleTypes if !ui.permissionCheck('ticket.agent') + group = ticket.group + return articleTypes if !group.email_address_id + + attributes = ['to', 'cc', 'subject'] + if !ui.Config.get('ui_ticket_zoom_article_email_subject') + attributes = ['to', 'cc'] + articleTypes.push { + name: 'email' + icon: 'email' + attributes: attributes + internal: false, + features: ['attachment'] + } + + articleTypes + + @setArticleType: (type, ticket, ui, signaturePosition) -> + + # detect current signature (use current group_id, if not set, use ticket.group_id) + ticketCurrent = App.Ticket.fullLocal(ticket.id) + group_id = ticketCurrent.group_id + task = App.TaskManager.get(ui.taskKey) + if task && task.state && task.state.ticket && task.state.ticket.group_id + group_id = task.state.ticket.group_id + group = App.Group.find(group_id) + signature = undefined + if group && group.signature_id + signature = App.Signature.find(group.signature_id) + + # add/replace signature + if signature && signature.body && type is 'email' + + # if signature has changed, remove it + signature_id = ui.$('[data-signature=true]').data('signature-id') + if signature_id && signature_id.toString() isnt signature.id.toString() + ui.$('[data-name=body] [data-signature="true"]').remove() + + # apply new signature + signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get(), ticket: ticketCurrent, config: App.Config.all() }) + + body = ui.$('[data-name=body]') + if App.Utils.signatureCheck(body.html() || '', signatureFinished) + if !App.Utils.htmlLastLineEmpty(body) + body.append('

') + signature = $("
#{signatureFinished}
") + App.Utils.htmlStrip(signature) + if signaturePosition is 'top' + body.prepend(signature) + else + body.append(signature) + ui.$('[data-name=body]').replaceWith(body) + + # remove old signature + else + ui.$('[data-name=body] [data-signature=true]').remove() + + if type isnt 'email' + ui.$('[name=to]').val('') + ui.$('[name=cc]').val('') + ui.$('[name=subject]').val('') + + @validation: (type, params, ui) -> + return true if type isnt 'email' + + # check if recipient exists + if _.isEmpty(params.to) && _.isEmpty(params.cc) + new App.ControllerModal( + head: 'Text missing' + buttonCancel: 'Cancel' + buttonCancelClass: 'btn--danger' + buttonSubmit: false + message: 'Need recipient in "To" or "Cc".' + shown: true + small: true + container: ui.el.closest('.content') + ) + return false + + # check if message exists + if _.isEmpty(params.body) + new App.ControllerModal( + head: 'Text missing' + buttonCancel: 'Cancel' + buttonCancelClass: 'btn--danger' + buttonSubmit: false + message: 'Text needed' + shown: true + small: true + container: ui.el.closest('.content') + ) + return false + + true + App.Config.set('200-EmailReply', EmailReply, 'TicketZoomArticleAction') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/facebook_reply.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/facebook_reply.coffee index 41c9ad4e8..e5cfc598d 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/facebook_reply.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/facebook_reply.coffee @@ -34,4 +34,28 @@ class FacebookReply true + @articleTypes: (articleTypes, ticket, ui) -> + return articleTypes if !ui.permissionCheck('ticket.agent') + + return articleTypes if !ticket || !ticket.create_article_type_id + + articleTypeCreate = App.TicketArticleType.find(ticket.create_article_type_id).name + if articleTypeCreate is 'facebook feed post' + articleTypes.push { + name: 'facebook feed comment' + icon: 'facebook' + attributes: [] + internal: false, + features: [] + } + articleTypes + + @params: (type, params, ui) -> + if type is 'facebook feed comment' + App.Utils.htmlRemoveRichtext(ui.$('[data-name=body]'), false) + params.content_type = 'text/plain' + params.body = App.Utils.html2text(params.body, true) + + params + App.Config.set('300-FacebookReply', FacebookReply, 'TicketZoomArticleAction') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/note.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/note.coffee new file mode 100644 index 000000000..e5b775056 --- /dev/null +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/note.coffee @@ -0,0 +1,23 @@ +class Note + @action: (actions, ticket, article, ui) -> + actions + + @perform: (articleContainer, type, ticket, article, ui) -> + true + + @articleTypes: (articleTypes, ticket, ui) -> + internal = false + if ui.permissionCheck('ticket.agent') + internal = ui.Config.get('ui_ticket_zoom_article_note_new_internal') + + articleTypes.push { + name: 'note' + icon: 'note' + attributes: [] + internal: internal, + features: ['attachment'] + } + + articleTypes + +App.Config.set('100-Note', Note, 'TicketZoomArticleAction') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/phone_reply.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/phone_reply.coffee new file mode 100644 index 000000000..b2f35e2fd --- /dev/null +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/phone_reply.coffee @@ -0,0 +1,19 @@ +class PhoneReply + @action: (actions, ticket, article, ui) -> + actions + + @perform: (articleContainer, type, ticket, article, ui) -> + true + + @articleTypes: (articleTypes, ticket, ui) -> + return articleTypes if !ui.permissionCheck('ticket.agent') + articleTypes.push { + name: 'phone' + icon: 'phone' + attributes: [] + internal: false, + features: ['attachment'] + } + articleTypes + +App.Config.set('100-PhoneReply', PhoneReply, 'TicketZoomArticleAction') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/telegram.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/telegram.coffee index 6f18a172d..edba450f8 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/telegram.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/telegram.coffee @@ -42,4 +42,38 @@ class TelegramReply true + @articleTypes: (articleTypes, ticket, ui) -> + return articleTypes if !ui.permissionCheck('ticket.agent') + + return articleTypes if !ticket || !ticket.create_article_type_id + + articleTypeCreate = App.TicketArticleType.find(ticket.create_article_type_id).name + + return articleTypes if articleTypeCreate isnt 'telegram personal-message' + articleTypes.push { + name: 'telegram personal-message' + icon: 'telegram' + attributes: [] + internal: false, + features: ['attachment'] + maxTextLength: 10000 + warningTextLength: 5000 + } + articleTypes + + @setArticleType: (type, ticket, ui) -> + return if type isnt 'telegram personal-message' + rawHTML = ui.$('[data-name=body]').html() + cleanHTML = App.Utils.htmlRemoveRichtext(rawHTML) + if cleanHTML && cleanHTML.html() != rawHTML + ui.$('[data-name=body]').html(cleanHTML) + + @params: (type, params, ui) -> + if type is 'telegram personal-message' + App.Utils.htmlRemoveRichtext(ui.$('[data-name=body]'), false) + params.content_type = 'text/plain' + params.body = App.Utils.html2text(params.body, true) + + params + App.Config.set('300-TelegramReply', TelegramReply, 'TicketZoomArticleAction') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/twitter_reply.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/twitter_reply.coffee index 6ce871868..1a39dd606 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/twitter_reply.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/twitter_reply.coffee @@ -125,4 +125,70 @@ class TwitterReply article: articleNew }) + @articleTypes: (articleTypes, ticket, ui) -> + return articleTypes if !ui.permissionCheck('ticket.agent') + + return articleTypes if !ticket || !ticket.create_article_type_id + + articleTypeCreate = App.TicketArticleType.find(ticket.create_article_type_id).name + if articleTypeCreate is 'twitter status' + attributes = ['body:limit', 'body:initials'] + if !ui.Config.get('ui_ticket_zoom_article_twitter_initials') + attributes = ['body:limit'] + articleTypes.push { + name: 'twitter status' + icon: 'twitter' + attributes: [] + internal: false, + features: attributes + maxTextLength: 280 + warningTextLength: 30 + } + else if articleTypeCreate is 'twitter direct-message' + attributes = ['body:limit', 'body:initials'] + if !ui.Config.get('ui_ticket_zoom_article_twitter_initials') + attributes = ['body:limit'] + articleTypes.push { + name: 'twitter direct-message' + icon: 'twitter' + attributes: ['to'] + internal: false, + features: attributes + maxTextLength: 10000 + warningTextLength: 500 + } + + articleTypes + + @validation: (type, params, ui) -> + if type is 'twitter status' + textLength = ui.maxTextLength - App.Utils.textLengthWithUrl(params.body) + return false if textLength < 0 + + if params.type is 'twitter direct-message' + textLength = ui.maxTextLength - App.Utils.textLengthWithUrl(params.body) + return false if textLength < 0 + + true + + @setArticleType: (type, ticket, ui) -> + return if type isnt 'twitter status' && type isnt 'twitter direct-message' + rawHTML = ui.$('[data-name=body]').html() + cleanHTML = App.Utils.htmlRemoveRichtext(rawHTML) + if cleanHTML && cleanHTML.html() != rawHTML + ui.$('[data-name=body]').html(cleanHTML) + + @params: (type, params, ui) -> + if type is 'twitter status' + App.Utils.htmlRemoveRichtext(ui.$('[data-name=body]'), false) + params.content_type = 'text/plain' + params.body = App.Utils.html2text(params.body, true) + + if type is 'twitter direct-message' + App.Utils.htmlRemoveRichtext(ui.$('[data-name=body]'), false) + params.content_type = 'text/plain' + params.body = App.Utils.html2text(params.body, true) + + params + App.Config.set('300-TwitterReply', TwitterReply, 'TicketZoomArticleAction') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee index 2795d7e46..9c0b3d070 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee @@ -92,112 +92,13 @@ class App.TicketZoomArticleNew extends App.Controller ) setPossibleArticleTypes: => - possibleArticleType = - note: true - phone: true - if @ticket && @ticket.create_article_type_id - articleTypeCreate = App.TicketArticleType.find(@ticket.create_article_type_id).name - if articleTypeCreate is 'twitter status' - possibleArticleType['twitter status'] = true - else if articleTypeCreate is 'twitter direct-message' - possibleArticleType['twitter direct-message'] = true - else if articleTypeCreate is 'email' - possibleArticleType['email'] = true - else if articleTypeCreate is 'facebook feed post' - possibleArticleType['facebook feed comment'] = true - else if articleTypeCreate is 'telegram personal-message' - possibleArticleType['telegram personal-message'] = true - if @ticket && @ticket.customer_id - customer = App.User.find(@ticket.customer_id) - if customer.email - possibleArticleType['email'] = true - - # gets referenced in @setArticleType + actionConfig = App.Config.get('TicketZoomArticleAction') + keys = _.keys(actionConfig).sort() @articleTypes = [] - if possibleArticleType.note - internal = @Config.get('ui_ticket_zoom_article_note_new_internal') - @articleTypes.push { - name: 'note' - icon: 'note' - attributes: [] - internal: internal, - features: ['attachment'] - } - if possibleArticleType.email - attributes = ['to', 'cc', 'subject'] - if !@Config.get('ui_ticket_zoom_article_email_subject') - attributes = ['to', 'cc'] - @articleTypes.push { - name: 'email' - icon: 'email' - attributes: attributes - internal: false, - features: ['attachment'] - } - if possibleArticleType['facebook feed comment'] - @articleTypes.push { - name: 'facebook feed comment' - icon: 'facebook' - attributes: [] - internal: false, - features: [] - } - if possibleArticleType['twitter status'] - attributes = ['body:limit', 'body:initials'] - if !@Config.get('ui_ticket_zoom_article_twitter_initials') - attributes = ['body:limit'] - @articleTypes.push { - name: 'twitter status' - icon: 'twitter' - attributes: [] - internal: false, - features: attributes - maxTextLength: 280 - warningTextLength: 30 - } - if possibleArticleType['twitter direct-message'] - attributes = ['body:limit', 'body:initials'] - if !@Config.get('ui_ticket_zoom_article_twitter_initials') - attributes = ['body:limit'] - @articleTypes.push { - name: 'twitter direct-message' - icon: 'twitter' - attributes: ['to'] - internal: false, - features: attributes - maxTextLength: 10000 - warningTextLength: 500 - } - if possibleArticleType.phone - @articleTypes.push { - name: 'phone' - icon: 'phone' - attributes: [] - internal: false, - features: ['attachment'] - } - if possibleArticleType['telegram personal-message'] - @articleTypes.push { - name: 'telegram personal-message' - icon: 'telegram' - attributes: [] - internal: false, - features: ['attachment'] - maxTextLength: 10000 - warningTextLength: 5000 - } - - if @permissionCheck('ticket.customer') - @type = 'note' - @articleTypes = [ - { - name: 'note' - icon: 'note' - attributes: [] - internal: false, - features: ['attachment'] - }, - ] + for key in keys + config = actionConfig[key] + if config && config.articleTypes + @articleTypes = config.articleTypes(@articleTypes, @ticket, @) placeCaretAtEnd: (el) -> el.focus() @@ -356,25 +257,13 @@ class App.TicketZoomArticleNew extends App.Controller else params.internal = false - if params.type is 'twitter status' - App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false) - params.content_type = 'text/plain' - params.body = App.Utils.html2text(params.body, true) - - if params.type is 'twitter direct-message' - App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false) - params.content_type = 'text/plain' - params.body = App.Utils.html2text(params.body, true) - - if params.type is 'facebook feed comment' - App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false) - params.content_type = 'text/plain' - params.body = App.Utils.html2text(params.body, true) - - if params.type is 'telegram personal-message' - App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false) - params.content_type = 'text/plain' - params.body = App.Utils.html2text(params.body, true) + # backend based validation + actionConfig = App.Config.get('TicketZoomArticleAction') + keys = _.keys(actionConfig).sort() + for key in keys + config = actionConfig[key] + if config && config.params + params = config.params(params.type, params, @) # add initals? for articleType in @articleTypes @@ -406,37 +295,6 @@ class App.TicketZoomArticleNew extends App.Controller ) return false - # validate email params - if params.type is 'email' - - # check if recipient exists - if !params.to && !params.cc - new App.ControllerModal( - head: 'Text missing' - buttonCancel: 'Cancel' - buttonCancelClass: 'btn--danger' - buttonSubmit: false - message: 'Need recipient in "To" or "Cc".' - shown: true - small: true - container: @el.closest('.content') - ) - return false - - # check if message exists - if !params.body - new App.ControllerModal( - head: 'Text missing' - buttonCancel: 'Cancel' - buttonCancelClass: 'btn--danger' - buttonSubmit: false - message: 'Text needed' - shown: true - small: true - container: @el.closest('.content') - ) - return false - # check attachment if params.body && attachmentCount < 1 matchingWord = App.Utils.checkAttachmentReference(params.body) @@ -444,13 +302,13 @@ class App.TicketZoomArticleNew extends App.Controller if !confirm(App.i18n.translateContent('You use %s in text but no attachment is attached. Do you want to continue?', matchingWord)) return false - if params.type is 'twitter status' - textLength = @maxTextLength - App.Utils.textLengthWithUrl(params.body) - return false if textLength < 0 - - if params.type is 'twitter direct-message' - textLength = @maxTextLength - App.Utils.textLengthWithUrl(params.body) - return false if textLength < 0 + # backend based validation + actionConfig = App.Config.get('TicketZoomArticleAction') + keys = _.keys(actionConfig).sort() + for key in keys + config = actionConfig[key] + if config && config.validation + return false if !config.validation(params.type, params, @) true @@ -516,50 +374,12 @@ class App.TicketZoomArticleNew extends App.Controller else @setArticleInternal(false) - # detect current signature (use current group_id, if not set, use ticket.group_id) - ticketCurrent = App.Ticket.fullLocal(@ticket_id) - group_id = ticketCurrent.group_id - task = App.TaskManager.get(@taskKey) - if task && task.state && task.state.ticket && task.state.ticket.group_id - group_id = task.state.ticket.group_id - group = App.Group.find(group_id) - signature = undefined - if group && group.signature_id - signature = App.Signature.find(group.signature_id) - - # add/replace signature - if signature && signature.body && @type is 'email' - - # if signature has changed, remove it - signature_id = @$('[data-signature=true]').data('signature-id') - if signature_id && signature_id.toString() isnt signature.id.toString() - @$('[data-name=body] [data-signature="true"]').remove() - - # apply new signature - signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get(), ticket: ticketCurrent, config: App.Config.all() }) - - body = @$('[data-name=body]') - if App.Utils.signatureCheck(body.html() || '', signatureFinished) - if !App.Utils.htmlLastLineEmpty(body) - body.append('

') - signature = $("
#{signatureFinished}
") - App.Utils.htmlStrip(signature) - if signaturePosition is 'top' - body.prepend(signature) - else - body.append(signature) - @$('[data-name=body]').replaceWith(body) - - # remove old signature - else - @$('[data-name=body] [data-signature=true]').remove() - - # remove richtext - if @type is 'twitter status' || @type is 'twitter direct-message' || @type is 'telegram personal-message' - rawHTML = @$('[data-name=body]').html() - cleanHTML = App.Utils.htmlRemoveRichtext(rawHTML) - if cleanHTML && cleanHTML.html() != rawHTML - @$('[data-name=body]').html(cleanHTML) + actionConfig = App.Config.get('TicketZoomArticleAction') + keys = _.keys(actionConfig).sort() + for key in keys + localConfig = actionConfig[key] + if localConfig && localConfig.setArticleType + localConfig.setArticleType(@type, @ticket, @, signaturePosition) # show/hide attributes/features @maxTextLength = undefined