# frozen_string_literal: true class TinyMceAsset < ApplicationRecord extend ProtocolsExporter extend MarvinJsActions include ActiveStorageConcerns include Canaid::Helpers::PermissionsHelper attr_accessor :reference before_create :set_reference after_create :calculate_estimated_size, :self_destruct after_destroy :release_team_space belongs_to :team, inverse_of: :tiny_mce_assets, optional: true belongs_to :object, polymorphic: true, optional: true, inverse_of: :tiny_mce_assets has_one_attached :image validates :estimated_size, presence: true def self.update_images(object, images, current_user) # image ids that are present in text text_images = object.public_send(Extends::RICH_TEXT_FIELD_MAPPINGS[object.class.name]) .scan(/data-mce-token="([^"]+)"/) .flatten images = JSON.parse(images) | text_images current_images = object.tiny_mce_assets.pluck(:id) images_to_delete = current_images.reject do |x| (images.include? Base62.encode(x)) end images.each do |image| image_to_update = find_by(id: Base62.decode(image)) # if image was pasted from another object, check permission and create a copy if image_to_update.object != object && image_to_update.can_read?(current_user) image_to_update.clone_tinymce_asset(object) image_to_update.reload end next if image_to_update.object image_to_update&.update(object: object, saved: true) create_create_marvinjs_activity(image_to_update, current_user) end where(id: images_to_delete).find_each do |image_to_delete| create_delete_marvinjs_activity(image_to_delete, current_user) image_to_delete.destroy end object.delay(queue: :assets).copy_unknown_tiny_mce_images(current_user) rescue StandardError => e Rails.logger.error e.message Rails.logger.error e.backtrace.join("\n") end def self.generate_url(description, obj = nil) # Check tinymce for old format description = update_old_tinymce(description, obj) description = Nokogiri::HTML(description) tm_assets = description.css('img[data-mce-token]') tm_assets.each do |tm_asset| asset_id = tm_asset.attr('data-mce-token') new_asset = obj.tiny_mce_assets.find_by(id: Base62.decode(asset_id)) if new_asset&.image&.attached? tm_asset.attributes['src'].value = Rails.application.routes.url_helpers.url_for(new_asset.image) tm_asset['class'] = 'img-responsive' end end description.css('body').inner_html.to_s end def file_name return '' unless image.attached? image.blob&.filename&.sanitized end def file_size return 0 unless image.attached? image.blob&.byte_size end def content_type return '' unless image.attached? image&.blob&.content_type end def preview image.variant(resize_to_limit: Constants::LARGE_PIC_FORMAT) end def self.delete_unsaved_image(id) asset = find_by(id: id) asset.destroy if asset && !asset.saved end def self.update_estimated_size(id) asset = find_by(id: id) return unless asset&.image&.attached? size = asset.image.blob.byte_size return if size.blank? e_size = size * Constants::ASSET_ESTIMATED_SIZE_FACTOR asset.update(estimated_size: e_size) Rails.logger.info "Asset #{id}: Estimated size successfully calculated" # update team space taken asset.team.take_space(e_size) asset.team.save end def self.update_old_tinymce(description, obj = nil, import = false) return description unless description description.scan(/\[~tiny_mce_id:(\w+)\]/).flatten.each do |token| old_format = /\[~tiny_mce_id:#{token}\]/ new_format = "" asset = find_by(id: token) # impor flag only for import from file cases, because we don't have image in DB unless asset || import # remove tag if asset deleted description.sub!(old_format, '') next end # If object (step or result text) don't have direct assciation to tinyMCE image, we need copy it. asset.clone_tinymce_asset(obj) if obj && obj != asset.object description.sub!(old_format, new_format) end description end def self.save_to_eln(ostream, dir) if exists? order(:id).each do |tiny_mce_asset| asset_guid = get_guid(tiny_mce_asset.id) extension = tiny_mce_asset.image.blob.filename.extension asset_file_name = if extension.blank? "rte-#{asset_guid}" else "rte-#{asset_guid}.#{tiny_mce_asset.image.blob.filename.extension}" end ostream.put_next_entry("#{dir}/#{asset_file_name}") ostream.print(tiny_mce_asset.image.download) end end ostream end def clone_tinymce_asset(obj) team_id = Team.search_by_object(object)&.id return false unless team_id tiny_img_clone = TinyMceAsset.new( estimated_size: estimated_size, object: obj, team_id: team_id ) tiny_img_clone.transaction do tiny_img_clone.save! duplicate_file(tiny_img_clone) end return false unless tiny_img_clone.persisted? obj.tiny_mce_assets << tiny_img_clone # Prepare array of image to update cloned_img_ids = [[id, tiny_img_clone.id]] obj_field = Extends::RICH_TEXT_FIELD_MAPPINGS[obj.class.name] # Update description with new format obj.update(obj_field => TinyMceAsset.update_old_tinymce(obj[obj_field])) # reassign images obj.reassign_tiny_mce_image_references(cloned_img_ids) end def blob image&.blob end def duplicate_file(to_asset) return unless image.attached? raise ArgumentError, 'Destination TinyMce asset should be persisted first!' unless to_asset.persisted? image.blob.open do |tmp_file| to_blob = ActiveStorage::Blob.create_and_upload!(io: tmp_file, filename: blob.filename, metadata: blob.metadata) to_asset.image.attach(to_blob) end TinyMceAsset.update_estimated_size(to_asset.id) end def can_read?(user) case object_type when 'MyModule' can_read_my_module?(user, object) when 'Protocol' can_read_protocol_in_module?(user, object) || can_read_protocol_in_repository?(user, object) when 'ResultText' can_read_result?(user, object.result) when 'StepText' protocol = object.step_orderable_element.step.protocol can_read_protocol_in_module?(user, protocol) || can_read_protocol_in_repository?(user, protocol) when 'Step' can_read_protocol_in_module?(user, step.protocol) || can_read_protocol_in_repository?(user, step.protocol) else false end end private def self_destruct TinyMceAsset.delay(queue: :assets, run_at: 1.day.from_now).delete_unsaved_image(id) end def calculate_estimated_size TinyMceAsset.delay(queue: :assets, run_at: 5.minutes.from_now).update_estimated_size(id) end def release_team_space team.release_space(estimated_size) team.save end def set_reference obj_type = "#{@reference.class.to_s.underscore}=".to_sym public_send(obj_type, @reference) if @reference end end