inline_images.rb 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Sequencer::Unit::Import::Common::Ticket::Article::InlineImages < Sequencer::Unit::Base
  3. include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
  4. uses :mapped, :instance
  5. def process
  6. check_for_existing_instance
  7. return if !contains_inline_image?(mapped[:body])
  8. replaced_body = replaced_inline_images
  9. (replaced_body, inline_attachments) = HtmlSanitizer.replace_inline_images(replaced_body, mapped[:ticket_id])
  10. provide_mapped do
  11. {
  12. body: replaced_body.strip,
  13. attachments: inline_attachments,
  14. }
  15. end
  16. end
  17. def self.inline_data(image_url)
  18. clean_image_url = image_url.gsub(%r{^cid:}, '')
  19. return if !%r{^(http|https)://.+?$}.match?(clean_image_url)
  20. @cache ||= {}
  21. return @cache[clean_image_url] if @cache[clean_image_url]
  22. image_data = download(clean_image_url)
  23. return if image_data.blank?
  24. @cache[clean_image_url] = "data:image/png;base64,#{Base64.strict_encode64(image_data)}"
  25. @cache[clean_image_url]
  26. end
  27. def self.download(image_url)
  28. logger.debug { "Downloading inline image from #{image_url}" }
  29. response = UserAgent.get(
  30. image_url,
  31. {},
  32. {
  33. open_timeout: 20,
  34. read_timeout: 240,
  35. verify_ssl: true,
  36. },
  37. )
  38. return response.body if response.success?
  39. logger.error response.error
  40. nil
  41. end
  42. private
  43. def contains_inline_image?(string)
  44. return false if string.blank?
  45. string.include?(inline_image_url_prefix)
  46. end
  47. def replaced_inline_images
  48. body_html = Nokogiri::HTML(mapped[:body])
  49. body_html.css('img').each do |node|
  50. next if !contains_inline_image?(node['src'])
  51. node.attributes['src'].value = self.class.inline_data(node['src'])
  52. end
  53. body_html.to_html
  54. end
  55. def check_for_existing_instance
  56. return if instance.blank? || local_inline_attachments.blank?
  57. local_inline_attachments.each(&:delete)
  58. end
  59. def local_inline_attachments
  60. @local_inline_attachments ||= instance.attachments&.filter { |attachment| attachment.preferences&.dig('Content-Disposition') == 'inline' }
  61. end
  62. def inline_image_url_prefix
  63. raise NotImplementedError
  64. end
  65. end