2019-03-11 20:43:50 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module TinyMceImages
|
|
|
|
extend ActiveSupport::Concern
|
|
|
|
|
|
|
|
included do
|
|
|
|
has_many :tiny_mce_assets,
|
|
|
|
as: :object,
|
|
|
|
class_name: :TinyMceAsset,
|
|
|
|
dependent: :destroy
|
|
|
|
|
2023-01-16 23:35:07 +08:00
|
|
|
before_validation :extract_base64_images
|
2023-01-19 15:58:05 +08:00
|
|
|
before_save :clean_tiny_mce_image_urls
|
2023-02-13 23:03:58 +08:00
|
|
|
after_create :ensure_extracted_image_object_references
|
2019-04-23 21:16:40 +08:00
|
|
|
|
2019-12-05 20:27:17 +08:00
|
|
|
def prepare_for_report(field, base64_encoded_imgs = false)
|
2019-03-11 20:43:50 +08:00
|
|
|
description = self[field]
|
2019-05-08 20:29:25 +08:00
|
|
|
|
|
|
|
# Check tinymce for old format
|
2019-05-14 21:01:57 +08:00
|
|
|
description = TinyMceAsset.update_old_tinymce(description, self)
|
2019-05-08 20:29:25 +08:00
|
|
|
|
2019-03-20 21:40:18 +08:00
|
|
|
tiny_mce_assets.each do |tm_asset|
|
2019-09-26 22:27:22 +08:00
|
|
|
next unless tm_asset&.image&.attached?
|
2021-05-06 23:33:58 +08:00
|
|
|
begin
|
|
|
|
new_tm_asset_src =
|
|
|
|
if base64_encoded_imgs
|
|
|
|
tm_asset.convert_variant_to_base64(tm_asset.preview)
|
|
|
|
else
|
2023-07-12 21:42:34 +08:00
|
|
|
tm_asset.preview.processed.url(expires_in: Constants::URL_LONG_EXPIRE_TIME)
|
2021-05-06 23:33:58 +08:00
|
|
|
end
|
|
|
|
rescue ActiveStorage::FileNotFoundError
|
|
|
|
next
|
|
|
|
end
|
2019-07-05 22:56:05 +08:00
|
|
|
html_description = Nokogiri::HTML(description)
|
|
|
|
tm_asset_to_update = html_description.css(
|
|
|
|
"img[data-mce-token=\"#{Base62.encode(tm_asset.id)}\"]"
|
|
|
|
)[0]
|
|
|
|
next unless tm_asset_to_update
|
|
|
|
|
|
|
|
tm_asset_to_update.attributes['src'].value = new_tm_asset_src
|
|
|
|
description = html_description.css('body').inner_html.to_s
|
2019-03-11 20:43:50 +08:00
|
|
|
end
|
|
|
|
description
|
|
|
|
end
|
2019-03-22 17:52:26 +08:00
|
|
|
|
|
|
|
def tinymce_render(field)
|
2019-05-14 21:01:57 +08:00
|
|
|
TinyMceAsset.generate_url(self[field], self)
|
2019-03-22 17:52:26 +08:00
|
|
|
end
|
2019-04-23 21:16:40 +08:00
|
|
|
|
2023-08-04 17:24:27 +08:00
|
|
|
def shareable_tinymce_render(field)
|
|
|
|
TinyMceAsset.generate_url(self[field], self, is_shared_object: true)
|
|
|
|
end
|
|
|
|
|
2019-04-23 21:16:40 +08:00
|
|
|
# Takes array of old/new TinyMCE asset ID pairs
|
|
|
|
# and updates references in assosiated object's description
|
|
|
|
def reassign_tiny_mce_image_references(images = [])
|
|
|
|
object_field = Extends::RICH_TEXT_FIELD_MAPPINGS[self.class.name]
|
2022-05-24 17:13:47 +08:00
|
|
|
|
|
|
|
return unless object_field
|
|
|
|
|
2019-05-08 20:29:25 +08:00
|
|
|
description = read_attribute(object_field)
|
2019-05-08 20:35:28 +08:00
|
|
|
|
2023-01-09 16:52:34 +08:00
|
|
|
return unless description
|
|
|
|
|
2019-05-08 20:29:25 +08:00
|
|
|
# Check tinymce for old format
|
2019-05-14 21:01:57 +08:00
|
|
|
description = TinyMceAsset.update_old_tinymce(description, self)
|
2019-05-08 20:29:25 +08:00
|
|
|
|
|
|
|
parsed_description = Nokogiri::HTML(description)
|
2019-04-23 21:16:40 +08:00
|
|
|
images.each do |image|
|
|
|
|
old_id = image[0]
|
|
|
|
new_id = image[1]
|
|
|
|
image = parsed_description.at_css("img[data-mce-token=\"#{Base62.encode(old_id)}\"]")
|
2019-05-22 22:13:40 +08:00
|
|
|
|
|
|
|
unless image
|
|
|
|
Rails.logger.error "TinyMCE Asset with id #{old_id} not in text"
|
|
|
|
next
|
|
|
|
end
|
2019-05-27 22:27:27 +08:00
|
|
|
|
2019-04-23 21:16:40 +08:00
|
|
|
image['data-mce-token'] = Base62.encode(new_id)
|
|
|
|
end
|
2019-05-15 20:13:13 +08:00
|
|
|
update(object_field => parsed_description.css('body').inner_html.to_s)
|
2019-04-23 21:16:40 +08:00
|
|
|
end
|
|
|
|
|
2019-04-24 19:34:56 +08:00
|
|
|
def clone_tinymce_assets(target, team)
|
|
|
|
cloned_img_ids = []
|
|
|
|
tiny_mce_assets.each do |tiny_img|
|
2019-10-04 21:59:22 +08:00
|
|
|
next unless tiny_img.image.attached? && tiny_img.image.service.exist?(tiny_img.image.blob.key)
|
2019-10-03 21:43:39 +08:00
|
|
|
|
2019-09-30 16:25:11 +08:00
|
|
|
tiny_img_clone = TinyMceAsset.create(
|
2019-04-24 19:34:56 +08:00
|
|
|
estimated_size: tiny_img.estimated_size,
|
|
|
|
object: target,
|
|
|
|
team: team
|
|
|
|
)
|
2019-07-05 22:56:05 +08:00
|
|
|
|
2019-09-30 16:25:11 +08:00
|
|
|
tiny_img.duplicate_file(tiny_img_clone)
|
2019-04-24 19:34:56 +08:00
|
|
|
target.tiny_mce_assets << tiny_img_clone
|
|
|
|
cloned_img_ids << [tiny_img.id, tiny_img_clone.id]
|
|
|
|
end
|
|
|
|
target.reassign_tiny_mce_image_references(cloned_img_ids)
|
|
|
|
end
|
|
|
|
|
2023-01-20 17:45:56 +08:00
|
|
|
def copy_unknown_tiny_mce_images(user)
|
2019-09-25 19:45:34 +08:00
|
|
|
asset_team_id = Team.search_by_object(self).id
|
2019-05-20 18:44:16 +08:00
|
|
|
return unless asset_team_id
|
|
|
|
|
|
|
|
object_field = Extends::RICH_TEXT_FIELD_MAPPINGS[self.class.name]
|
|
|
|
|
|
|
|
image_changed = false
|
|
|
|
parsed_description = Nokogiri::HTML(read_attribute(object_field))
|
|
|
|
parsed_description.css('img').each do |image|
|
2023-01-20 17:45:56 +08:00
|
|
|
asset = image['data-mce-token'].presence && TinyMceAsset.find_by(id: Base62.decode(image['data-mce-token']))
|
2019-05-20 18:44:16 +08:00
|
|
|
|
2023-01-20 17:45:56 +08:00
|
|
|
if asset
|
|
|
|
next if asset.object == self
|
|
|
|
next unless asset.can_read?(user)
|
2019-05-20 18:44:16 +08:00
|
|
|
else
|
2019-10-22 17:02:59 +08:00
|
|
|
url = image['src']
|
|
|
|
image_type = FastImage.type(url).to_s
|
2019-10-22 20:13:22 +08:00
|
|
|
next unless image_type
|
|
|
|
|
2019-11-13 20:45:30 +08:00
|
|
|
begin
|
|
|
|
new_image = Down.download(url, max_size: Rails.configuration.x.file_max_size_mb.megabytes)
|
|
|
|
rescue Down::TooLarge => e
|
|
|
|
Rails.logger.error e.message
|
|
|
|
next
|
|
|
|
end
|
2019-10-22 20:13:22 +08:00
|
|
|
|
2019-10-22 17:02:59 +08:00
|
|
|
new_image_filename = Asset.generate_unique_secure_token + '.' + image_type
|
2019-05-20 18:44:16 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
new_asset = TinyMceAsset.create(
|
|
|
|
object: self,
|
|
|
|
team_id: asset_team_id
|
|
|
|
)
|
|
|
|
|
2019-07-05 22:56:05 +08:00
|
|
|
new_asset.transaction do
|
|
|
|
new_asset.save!
|
2023-01-20 17:45:56 +08:00
|
|
|
if asset
|
2019-08-05 17:30:36 +08:00
|
|
|
asset.duplicate_file(new_asset)
|
|
|
|
else
|
|
|
|
new_asset.image.attach(io: new_image, filename: new_image_filename)
|
|
|
|
end
|
2019-07-05 22:56:05 +08:00
|
|
|
end
|
|
|
|
|
2019-05-20 18:44:16 +08:00
|
|
|
image['src'] = ''
|
|
|
|
image['class'] = 'img-responsive'
|
|
|
|
image['data-mce-token'] = Base62.encode(new_asset.id)
|
|
|
|
image_changed = true
|
|
|
|
end
|
|
|
|
update(object_field => parsed_description.css('body').inner_html.to_s) if image_changed
|
|
|
|
rescue StandardError => e
|
|
|
|
Rails.logger.error "Object: #{self.class.name}, id: #{id}, error: #{e.message}"
|
|
|
|
end
|
|
|
|
|
2019-04-23 21:16:40 +08:00
|
|
|
private
|
|
|
|
|
|
|
|
def clean_tiny_mce_image_urls
|
|
|
|
object_field = Extends::RICH_TEXT_FIELD_MAPPINGS[self.class.name]
|
|
|
|
return unless changed.include?(object_field.to_s)
|
2019-04-24 19:34:56 +08:00
|
|
|
|
2019-04-23 21:16:40 +08:00
|
|
|
image_changed = false
|
|
|
|
parsed_description = Nokogiri::HTML(read_attribute(object_field))
|
|
|
|
parsed_description.css('img[data-mce-token]').each do |image|
|
|
|
|
image['src'] = ''
|
|
|
|
image['class'] = 'img-responsive'
|
|
|
|
image_changed = true
|
|
|
|
end
|
2023-04-04 19:52:35 +08:00
|
|
|
self[object_field] = parsed_description.to_html.strip if image_changed
|
2019-04-23 21:16:40 +08:00
|
|
|
end
|
2023-01-12 20:24:31 +08:00
|
|
|
|
|
|
|
def extract_base64_images
|
|
|
|
# extracts and uploads any base64 encoded images,
|
|
|
|
# so they get stored as files instead of directly in the text
|
2023-02-13 23:03:58 +08:00
|
|
|
@extracted_base64_images = []
|
2023-01-12 20:24:31 +08:00
|
|
|
|
|
|
|
object_field = Extends::RICH_TEXT_FIELD_MAPPINGS[self.class.name]
|
2023-01-16 17:58:37 +08:00
|
|
|
return unless object_field
|
2023-01-12 20:24:31 +08:00
|
|
|
|
2023-01-19 15:58:05 +08:00
|
|
|
sanitized_text = public_send(object_field)
|
|
|
|
return unless sanitized_text
|
2023-01-12 20:24:31 +08:00
|
|
|
|
|
|
|
ActiveRecord::Base.transaction do
|
2023-01-19 15:58:05 +08:00
|
|
|
sanitized_text.scan(/src="(data:image\/[^;]+;base64[^"]+)"/i).flatten.each do |base64_src|
|
2023-02-17 19:14:08 +08:00
|
|
|
base64_data_parts = base64_src.split('base64,')
|
|
|
|
base64_file_extension =
|
|
|
|
MIME::Types[
|
|
|
|
base64_data_parts.first.split(':').last[0..-2]
|
|
|
|
].first.preferred_extension
|
|
|
|
base64_data = base64_data_parts.last
|
2023-01-12 20:24:31 +08:00
|
|
|
|
|
|
|
tiny_image = TinyMceAsset.create!(
|
2023-01-17 17:49:08 +08:00
|
|
|
team: Team.search_by_object(self),
|
2023-01-19 15:58:05 +08:00
|
|
|
object_id: id,
|
|
|
|
object_type: self.class.name,
|
2023-01-12 20:24:31 +08:00
|
|
|
saved: true
|
|
|
|
)
|
|
|
|
|
|
|
|
tiny_image.image.attach(
|
|
|
|
io: StringIO.new(Base64.decode64(base64_data)),
|
2023-02-17 19:14:08 +08:00
|
|
|
filename: "#{Asset.generate_unique_secure_token}.#{base64_file_extension}"
|
2023-01-12 20:24:31 +08:00
|
|
|
)
|
|
|
|
|
2023-02-13 23:03:58 +08:00
|
|
|
@extracted_base64_images << tiny_image
|
|
|
|
|
2023-01-12 20:24:31 +08:00
|
|
|
encoded_id = Base62.encode(tiny_image.id)
|
|
|
|
|
2023-01-19 15:58:05 +08:00
|
|
|
sanitized_text.gsub!(
|
2023-01-12 20:24:31 +08:00
|
|
|
"#{base64_src}\"",
|
|
|
|
"\" data-mce-token=\"#{encoded_id}\" alt=\"description-#{encoded_id}\""
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-01-19 15:58:05 +08:00
|
|
|
assign_attributes(object_field => sanitized_text)
|
2023-01-12 20:24:31 +08:00
|
|
|
end
|
|
|
|
end
|
2023-02-13 23:03:58 +08:00
|
|
|
|
|
|
|
def ensure_extracted_image_object_references
|
|
|
|
# for models that were not yet in database in time of image extraction
|
|
|
|
# we need to update image references after creation
|
|
|
|
|
|
|
|
@extracted_base64_images&.each do |image|
|
|
|
|
next if image.object
|
|
|
|
|
|
|
|
image.update(object: self)
|
|
|
|
end
|
|
|
|
end
|
2019-03-11 20:43:50 +08:00
|
|
|
end
|
|
|
|
end
|