Skip to content

Commit

Permalink
Fixes zammad#2644 - Including Knowledge Base Answers into Ticket Arti…
Browse files Browse the repository at this point in the history
…cles doesn't attach Attachments
  • Loading branch information
mantas authored and thorsteneckel committed Apr 20, 2020
1 parent 3306de2 commit 880f1d5
Show file tree
Hide file tree
Showing 14 changed files with 226 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ class App.UiElement.richtext
for file in attribute.attachments
renderFile(file)

App.Event.bind('ui::ticket::addArticleAttachent', (data) ->
form_id = item.closest('form').find('[name=form_id]').val()

return if data.form_id isnt form_id
return if _.isEmpty(data.attachments)
for file in data.attachments
renderFile(file)
, form.form_id)

# remove items
item.find('.attachments').on('click', '.js-delete', (e) =>
id = $(e.currentTarget).data('id')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class App.TicketZoomArticleNew extends App.Controller

# add article attachment
@bind('ui::ticket::addArticleAttachent', (data) =>
return if data.ticket.id.toString() isnt @ticket_id.toString()
return if data.ticket?.id?.toString() isnt @ticket_id.toString() && data.form_id isnt @form_id
return if _.isEmpty(data.attachments)
for file in data.attachments
@renderAttachment(file)
Expand Down
32 changes: 28 additions & 4 deletions app/assets/javascripts/app/lib/base/jquery.textmodule.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,17 @@
if (trigger) {
var _this = this;

trigger.renderValue(this, elem, function(text) {
var form_id = this.$element.closest('form').find('[name=form_id]').val()

trigger.renderValue(this, elem, function(text, attachments) {
_this.cutInput()
_this.paste(text)
_this.close(true)

App.Event.trigger('ui::ticket::addArticleAttachent', {
attachments: attachments,
form_id: form_id
})
})
}
}
Expand Down Expand Up @@ -455,7 +462,7 @@

if (!item) return

callback(item.content)
callback(item.content, [])
}

Collection.renderResults = function(textmodule, term) {
Expand Down Expand Up @@ -497,6 +504,8 @@
var element = $('<li>').text(App.i18n.translateInline('Please wait...'))
textmodule.appendResults(element)

var form_id = textmodule.$element.closest('form').find('[name=form_id]').val()

App.Ajax.request({
id: 'textmoduleKbAnswer',
type: 'GET',
Expand All @@ -508,8 +517,23 @@

var body = translation.content().bodyWithPublicURLs()

App.Utils.htmlImage2DataUrlAsync(body, function(output){
callback(output)
App.Ajax.request({
id: 'textmoduleKbAnswerAttachments',
type: 'POST',
data: JSON.stringify({
form_id: form_id
}),
url: translation.parent().generateURL('/attachments/clone_to_form'),
success: function(data, status, xhr) {
translation.parent().attachments += data.attachments

App.Utils.htmlImage2DataUrlAsync(body, function(output){
callback(output, translation.parent().attachments)
})
},
error: function(xhr) {
callback('')
}
})
},
error: function(xhr) {
Expand Down
11 changes: 9 additions & 2 deletions app/controllers/knowledge_base/answer/attachments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

class KnowledgeBase::Answer::AttachmentsController < ApplicationController
prepend_before_action :authentication_check
prepend_before_action :authorize!

before_action :authorize!
before_action :fetch_answer

def create
Expand All @@ -18,6 +17,14 @@ def destroy
render json: @answer.assets({})
end

def clone_to_form
new_attachments = @answer.clone_attachments('UploadCache', params[:form_id], only_attached_attachments: true)

render json: {
attachments: new_attachments
}
end

private

def fetch_answer
Expand Down
79 changes: 79 additions & 0 deletions app/models/concerns/can_clone_attachments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
module CanCloneAttachments
extend ActiveSupport::Concern

=begin
clone existing attachments of article to the target object
article_parent = Ticket::Article.find(123)
article_new = Ticket::Article.find(456)
attached_attachments = article_parent.clone_attachments(article_new.class.name, article_new.id, only_attached_attachments: true)
inline_attachments = article_parent.clone_attachments(article_new.class.name, article_new.id, only_inline_attachments: true)
returns
[attachment1, attachment2, ...]
=end

def clone_attachments(object_type, object_id, options = {})
existing_attachments = Store.list(
object: object_type,
o_id: object_id,
)

is_html_content = false
if content_type.present? && content_type =~ %r{text/html}i
is_html_content = true
end

new_attachments = []
attachments.each do |new_attachment|
next if new_attachment.preferences['content-alternative'] == true

# only_attached_attachments mode is used by apply attached attachments to forwared article
if options[:only_attached_attachments] == true
if is_html_content == true

content_id = new_attachment.preferences['Content-ID'] || new_attachment.preferences['content_id']
next if content_id.present? && body.present? && body.match?(/#{Regexp.quote(content_id)}/i)
end
end

# only_inline_attachments mode is used when quoting HTML mail with #{article.body_as_html}
if options[:only_inline_attachments] == true
next if is_html_content == false
next if body.blank?

content_disposition = new_attachment.preferences['Content-Disposition'] || new_attachment.preferences['content_disposition']
next if content_disposition.present? && content_disposition !~ /inline/

content_id = new_attachment.preferences['Content-ID'] || new_attachment.preferences['content_id']
next if content_id.blank?
next if !body.match?(/#{Regexp.quote(content_id)}/i)
end

already_added = false
existing_attachments.each do |existing_attachment|
next if existing_attachment.filename != new_attachment.filename || existing_attachment.size != new_attachment.size

already_added = true
break
end
next if already_added == true

file = Store.add(
object: object_type,
o_id: object_id,
data: new_attachment.content,
filename: new_attachment.filename,
preferences: new_attachment.preferences,
)
new_attachments.push file
end

new_attachments
end
end
6 changes: 6 additions & 0 deletions app/models/knowledge_base/answer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class KnowledgeBase::Answer < ApplicationModel
include CanBePublished
include HasKnowledgeBaseAttachmentPermissions
include ChecksKbClientNotification
include CanCloneAttachments

AGENT_ALLOWED_ATTRIBUTES = %i[category_id promoted internal_note].freeze
AGENT_ALLOWED_NESTED_RELATIONS = %i[translations].freeze
Expand Down Expand Up @@ -96,6 +97,11 @@ def api_url
Rails.application.routes.url_helpers.knowledge_base_answer_path(category.knowledge_base, self)
end

# required by CanCloneAttachments
def content_type
'text/html'
end

private

def reordering_callback
Expand Down
77 changes: 1 addition & 76 deletions app/models/ticket/article.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Ticket::Article < ApplicationModel
include HasHistory
include ChecksHtmlSanitized
include CanCsvImport
include CanCloneAttachments
include HasObjectManagerAttributesValidation

include Ticket::Article::Assets
Expand Down Expand Up @@ -134,82 +135,6 @@ def attachments_inline
new_attachments
end

=begin
clone existing attachments of article to the target object
article_parent = Ticket::Article.find(123)
article_new = Ticket::Article.find(456)
attached_attachments = article_parent.clone_attachments(article_new.class.name, article_new.id, only_attached_attachments: true)
inline_attachments = article_parent.clone_attachments(article_new.class.name, article_new.id, only_inline_attachments: true)
returns
[attachment1, attachment2, ...]
=end

def clone_attachments(object_type, object_id, options = {})
existing_attachments = Store.list(
object: object_type,
o_id: object_id,
)

is_html_content = false
if content_type.present? && content_type =~ %r{text/html}i
is_html_content = true
end

new_attachments = []
attachments.each do |new_attachment|
next if new_attachment.preferences['content-alternative'] == true

# only_attached_attachments mode is used by apply attached attachments to forwared article
if options[:only_attached_attachments] == true
if is_html_content == true

content_id = new_attachment.preferences['Content-ID'] || new_attachment.preferences['content_id']
next if content_id.present? && body.present? && body.match?(/#{Regexp.quote(content_id)}/i)
end
end

# only_inline_attachments mode is used when quoting HTML mail with #{article.body_as_html}
if options[:only_inline_attachments] == true
next if is_html_content == false
next if body.blank?

content_disposition = new_attachment.preferences['Content-Disposition'] || new_attachment.preferences['content_disposition']
next if content_disposition.present? && content_disposition !~ /inline/

content_id = new_attachment.preferences['Content-ID'] || new_attachment.preferences['content_id']
next if content_id.blank?
next if !body.match?(/#{Regexp.quote(content_id)}/i)
end

already_added = false
existing_attachments.each do |existing_attachment|
next if existing_attachment.filename != new_attachment.filename || existing_attachment.size != new_attachment.size

already_added = true
break
end
next if already_added == true

file = Store.add(
object: object_type,
o_id: object_id,
data: new_attachment.content,
filename: new_attachment.filename,
preferences: new_attachment.preferences,
)
new_attachments.push file
end

new_attachments
end

def self.last_customer_agent_article(ticket_id)
sender = Ticket::Article::Sender.lookup(name: 'System')
Ticket::Article.where('ticket_id = ? AND sender_id NOT IN (?)', ticket_id, sender.id).order(created_at: :desc).first
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
class Controllers::KnowledgeBase::Answer::AttachmentsControllerPolicy < Controllers::ApplicationControllerPolicy
permit! :clone_to_form, to: 'knowledge_base.*'
default_permit!('knowledge_base.editor')
end
6 changes: 5 additions & 1 deletion config/routes/knowledge_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@
only: %i[create update show destroy],
concerns: :has_publishing do

resources :attachments, controller: 'knowledge_base/answer/attachments', only: %i[create destroy]
resources :attachments, controller: 'knowledge_base/answer/attachments', only: %i[create destroy] do
collection do
post :clone_to_form
end
end
end
end
end
Expand Down
16 changes: 16 additions & 0 deletions spec/factories/knowledge_base/answer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,21 @@
translation_traits { [:with_video] }
end
end

trait :with_attachment do
transient do
attachment { File.open('spec/fixtures/upload/hello_world.txt') }
end

after(:create) do |answer, context|
Store.add(
object: answer.class.name,
o_id: answer.id,
data: context.attachment.read,
filename: File.basename(context.attachment.path),
preferences: {}
)
end
end
end
end
7 changes: 7 additions & 0 deletions spec/models/knowledge_base/answer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@

it { is_expected.not_to validate_presence_of(:category_id) }
it { is_expected.to belong_to(:category) }
it { expect(kb_answer.attachments).to be_blank }

context 'with attachment' do
subject(:kb_answer) { create(:knowledge_base_answer, :with_attachment) }

it { expect(kb_answer.attachments).to be_present }
end
end
20 changes: 20 additions & 0 deletions spec/requests/knowledge_base/answer_attachments_cloning_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'rails_helper'

RSpec.describe 'KnowledgeBase answer attachments cloning', type: :request, authenticated_as: :agent_user do
include_context 'basic Knowledge Base' do
before do
published_answer
end
end

it 'copies to given UploadCache' do
form_id = Random.rand(999..9999)
endpoint = "/api/v1/knowledge_bases/#{knowledge_base.id}/answers/#{published_answer.id}/attachments/clone_to_form"
params = { form_id: form_id }

expect { post endpoint, params: params }
.to change { Store.list(object: 'UploadCache', o_id: form_id).count }
.from(0)
.to(1)
end
end
2 changes: 1 addition & 1 deletion spec/support/knowledge_base_contexts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
end

let :published_answer do
create(:knowledge_base_answer, category: category, published_at: 1.week.ago)
create(:knowledge_base_answer, :with_attachment, category: category, published_at: 1.week.ago)
end

let :published_answer_with_video do
Expand Down
Loading

0 comments on commit 880f1d5

Please sign in to comment.