# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ class Ticket::Article < ApplicationModel include LogsActivityStream include NotifiesClients include Historisable include HtmlSanitized load 'ticket/article/assets.rb' include Ticket::Article::Assets belongs_to :ticket has_one :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', foreign_key: :ticket_article_id, dependent: :destroy belongs_to :type, class_name: 'Ticket::Article::Type' belongs_to :sender, class_name: 'Ticket::Article::Sender' belongs_to :created_by, class_name: 'User' belongs_to :updated_by, class_name: 'User' store :preferences before_create :check_subject, :check_message_id_md5 before_update :check_subject, :check_message_id_md5 sanitized_html :body activity_stream_permission 'ticket.agent' activity_stream_attributes_ignored :type_id, :sender_id, :preferences history_attributes_ignored :type_id, :sender_id, :preferences, :message_id, :from, :to, :cc # fillup md5 of message id to search easier on very long message ids def check_message_id_md5 return if !message_id return if message_id_md5 self.message_id_md5 = Digest::MD5.hexdigest(message_id.to_s) end =begin insert inline image urls to body article_attributes = Ticket::Article.insert_urls(article_attributes) returns article_attributes_with_body_and_urls =end def self.insert_urls(article) return article if article['attachments'].empty? return article if article['content_type'] !~ %r{text/html}i return article if article['body'] !~ //im ) { |item| tag_start = $1 cid = $3 tag_end = $4 replace = item # look for attachment article['attachments'].each { |file| next if !file[:preferences] || !file[:preferences]['Content-ID'] || (file[:preferences]['Content-ID'] != cid && file[:preferences]['Content-ID'] != "<#{cid}>" ) replace = "#{tag_start}/api/v1/ticket_attachment/#{article['ticket_id']}/#{article['id']}/#{file[:id]}\"#{tag_end}>" inline_attachments[file[:id]] = true break } replace } new_attachments = [] article['attachments'].each { |file| next if inline_attachments[file[:id]] new_attachments.push file } article['attachments'] = new_attachments article end =begin get inline attachments of article article = Ticket::Article.find(123) attachments = article.attachments_inline returns [attachment1, attachment2, ...] =end def attachments_inline inline_attachments = {} body.gsub( //im ) { |_item| cid = $2 # look for attachment attachments.each { |file| next if !file.preferences['Content-ID'] || (file.preferences['Content-ID'] != cid && file.preferences['Content-ID'] != "<#{cid}>" ) inline_attachments[file.id] = true break } } new_attachments = [] attachments.each { |file| next if !inline_attachments[file.id] new_attachments.push file } 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 end =begin get body as html article = Ticket::Article.find(123) article.body_as_html =end def body_as_html return '' if !body return body if content_type && content_type =~ %r{text/html}i body.text2html end =begin get body as text article = Ticket::Article.find(123) article.body_as_text =end def body_as_text return '' if !body return body if !content_type || content_type.empty? || content_type =~ %r{text/plain}i body.html2text end =begin get body as text with quote sign "> " at the beginning of each line article = Ticket::Article.find(123) article.body_as_text =end def body_as_text_with_quote body_as_text.word_wrap.message_quote end =begin get article as raw (e. g. if it's a email, the raw email) article = Ticket::Article.find(123) article.as_raw returns: file # Store =end def as_raw list = Store.list( object: 'Ticket::Article::Mail', o_id: id, ) return if !list return if list.empty? return if !list[0] list[0] end =begin save article as raw (e. g. if it's a email, the raw email) article = Ticket::Article.find(123) article.save_as_raw(msg) returns: file # Store =end def save_as_raw(msg) Store.add( object: 'Ticket::Article::Mail', o_id: id, data: msg, filename: "ticket-#{ticket.number}-#{id}.eml", preferences: {}, created_by_id: created_by_id, ) end def sanitizeable?(attribute, _value) return true if attribute != :body return false if content_type.blank? content_type =~ /html/i end =begin get relation name of model based on params model = Model.find(1) attributes = model.attributes_with_association_names returns hash with attributes, association ids, association names and relation name =end def attributes_with_association_names attributes = super attributes['attachments'] = [] attachments.each { |attachment| item = { id: attachment['id'], filename: attachment['filename'], size: attachment['size'], preferences: attachment['preferences'], } attributes['attachments'].push item } Ticket::Article.insert_urls(attributes) end =begin get relations of model based on params model = Model.find(1) attributes = model.attributes_with_association_ids returns hash with attributes and association ids =end def attributes_with_association_ids attributes = super attributes['attachments'] = [] attachments.each { |attachment| item = { id: attachment['id'], filename: attachment['filename'], size: attachment['size'], preferences: attachment['preferences'], } attributes['attachments'].push item } if attributes['body'] && attributes['content_type'] =~ %r{text/html}i attributes['body'] = HtmlSanitizer.dynamic_image_size(attributes['body']) end Ticket::Article.insert_urls(attributes) end private # strip not wanted chars def check_subject return if !subject subject.gsub!(/\s|\t|\r/, ' ') end def history_log_attributes { related_o_id: self['ticket_id'], related_history_object: 'Ticket', } end # callback function to overwrite # default history stream log attributes # gets called from activity_stream_log def activity_stream_log_attributes { group_id: Ticket.find(ticket_id).group_id, } end class Flag < ApplicationModel end class Sender < ApplicationModel include LatestChangeObserved validates :name, presence: true end class Type < ApplicationModel include LatestChangeObserved validates :name, presence: true end end