module Fiona7 module Builder class ObjBuilder end end end require 'fiona7/type_register' require 'fiona7/recursive_link_resolver' #require 'fiona7/write_obj' #require 'fiona7/released_obj' require 'fiona7/builder/batch_widget_writer' require 'fiona7/link_converter/scrivito_to_fiona' require 'fiona7/widget_resolver' module Fiona7 module Builder class ObjBuilder def initialize(values) @values = values.symbolize_keys # garbage @values.delete(:_modification) # revert command sends this info. which is silly. @values.delete(:_last_changed) #puts "CREATING/UPDATING: #{@values.inspect}" end def build assert_valid prepare_object write_widget_pool store_attributes @obj end def validate true end protected def update? false end def assert_valid validate || (raise Scrivito::ClientError.new(@error_msg || "Invalid input", 422)) end def prepare_object @path = @values.delete(:_path) || generate_orphaned_path @obj_class = @values.delete(:_obj_class) @real_obj_class = TypeRegister::NameAliaser.new(virtual: @obj_class).real_obj_class @widget_pool = @values.delete(:_widget_pool) @permalink = @values.delete(:_permalink) @path = "/#{@path}" unless @path.start_with?('/') @name, parent_path = name_and_parent_path_from_path(@path) @parent = ensure_parent_exists(parent_path) if parent_path ensure_obj_class_exists # bottom one is faster, but can only work when there are no widgets and no uploads if (@widget_pool && !@widget_pool.empty?) || (@values.any? {|_, (_, v)| v.kind_of?(File) || v.kind_of?(ActionDispatch::Http::UploadedFile) } ) @obj = WriteObj.create!(name: @name, parent_obj_id: @parent.id, obj_class: @real_obj_class) else @obj = WriteObj.create!(name: @name, parent_obj_id: @parent.id, obj_class: @real_obj_class) end end def write_widget_pool resolver = WidgetResolver.new(@obj.attr_values["X_widget_pool"]||[], WriteObj) @id_map = resolver.id_map @widget_path_map = resolver.path_map if @widget_pool && !@widget_pool.empty? @new_full_text = rewrite_full_text @widgets = BatchWidgetWriter.new(@obj, @id_map, @widget_pool, @widget_path_map) @widgets.write # FIXME: refactor this code if @widgets.pool_changed? # after widget writing this either has new widgets # or some widgets have been removed @new_widget_pool = @widget_pool.map do |widget_id, definition| # widget deleted next if definition.nil? {title: widget_id, destination_object: @widget_path_map[widget_id]} end.compact # newly added widgets @widget_path_map.each do |widget_id, path| next if !@widget_pool[widget_id].nil? @new_widget_pool << {title: widget_id, destination_object: path} end end end end def store_attributes if @obj.obj_class != @real_obj_class @obj.obj_class = @real_obj_class @obj.save! @obj.reload end @obj.send(:reload_attributes) type_definition = Fiona7::TypeRegister.instance.write(@obj_class) if type_definition.nil? raise "Definition not found for #{@obj_class}" end @values.each do |attribute_name, possible_pair| (claimed_type, value) = *possible_pair attribute = type_definition.find_attribute(attribute_name) if attribute.nil? #debugger raise "Attribute #{attribute_name} not found in #{@obj_class}" end virtual_name = attribute.name.to_sym attribute_name = real_name = attribute.real_name.to_sym virtual_type = attribute.type.to_sym case virtual_type when :linklist links = (value || []).map do |link| link = link.symbolize_keys new_link = {} if link[:url] # handle www.example.com new_link[:url] = "external:#{link[:url]}" unless link[:url] =~ /\A[a-zA-Z][a-zA-Z0-9+.-]:/ elsif obj_id = link[:obj_id] new_link[:url] = WriteObj.find(obj_id).path end new_link[:url] ||= link[:url] new_link[:title] = link[:title] if link[:title] new_link[:target] = link[:target] if link[:target] new_link[:url] = "#{new_link[:url]}?#{link[:query]}" if link[:query] new_link[:url] = "#{new_link[:url]}##{link[:fragment]}" if link[:fragment] new_link end @obj.set(attribute_name.to_s, links) when :link link = (value || {}).symbolize_keys new_link = {} if link[:url] # handle www.example.com new_link[:url] = "external:#{link[:url]}" unless link[:url] =~ /\A[a-zA-Z][a-zA-Z0-9+.-]:/ elsif obj_id = link[:obj_id] new_link[:url] = WriteObj.find(obj_id).path end new_link[:url] ||= link[:url] new_link[:title] = link[:title] if link[:title] new_link[:target] = link[:target] if link[:target] new_link[:url] = "#{new_link[:url]}?#{link[:query]}" if link[:query] new_link[:url] = "#{new_link[:url]}##{link[:fragment]}" if link[:fragment] new_link if link.empty? @obj.set(attribute_name.to_s, []) else @obj.set(attribute_name.to_s, [new_link]) end when :reference obj = WriteObj.find(value) rescue nil if obj @obj.set(attribute_name.to_s, [obj.path]) else @obj.set(attribute_name.to_s, []) end when :referencelist ids = value || [] if ids.empty? @obj.set(attribute_name.to_s, []) else objects_map = Hash[WriteObj.where(obj_id: ids).map {|o| [o.id.to_s, o]}] objects = ids.map {|id| objects_map[id] }.compact @obj.set(attribute_name, objects) end when :widgetlist links = [] if value.kind_of?(Array) (value || []).each do |widget_id| if (path=@widget_path_map[widget_id]||@widget_path_map[widget_id.to_s]) links << {destination_object: path, title: widget_id} else raise Scrivito::ScrivitoError, "Inconsistent widget pool state detected, unable to store widgets (unable to find #{widget_id} in #{@widget_path_map.inspect})" end end elsif value.kind_of?(Hash) # stupid shits could not use one consistent format (value || {})["list"].each do |crap| widget_id = crap["widget"] if (path=@widget_path_map[widget_id]||@widget_path_map[widget_id.to_s]) links << {destination_object: path, title: widget_id} else raise Scrivito::ScrivitoError, "Inconsistent widget pool state detected, unable to store widgets (unable to find #{widget_id} in #{@widget_path_map.inspect})" end end end @obj.set(attribute_name.to_s, links) when :text, :string, :enum, :multienum, :date #if value.nil? && value != @obj[attribute_name] @obj.set(attribute_name.to_s, value) #end when :stringlist if Fiona7.mode == :legacy && attribute_name.to_s == "channels" @obj.set(:channels, value || []) else @obj.set(attribute_name.to_s, value.to_json) end when :html converted_links = LinkConverter::ScrivitoToFiona.new(WriteObj, value.to_s).convert @obj.set(attribute_name.to_s, converted_links) when :binary if value.kind_of?(String) # NOTE: this code path has not been tested yet. target = {title: value, destination_object: InternalReleasedObj.find(value.to_i)} @obj.set(attribute_name.to_s, target) elsif value.kind_of?(File) if !Fiona7.mode == :legacy || attribute_name.to_s != "blob" @obj.set(attribute_name.to_s, upload_file(value)) else special_upload_handling(attribute_name, value) end elsif value.kind_of?(ActionDispatch::Http::UploadedFile) if !Fiona7.mode == :legacy || attribute_name.to_s != "blob" @obj.set(attribute_name.to_s, upload_file(value)) else special_upload_uploaded_handling(attribute_name, value) end elsif value.nil? @obj.set(attribute_name.to_s, []) elsif value.kind_of?(Hash) #TODO: legacy mode! target = {title: value["id"], destination_object: InternalReleasedObj.find(value["id"].to_i)} @obj.set(attribute_name.to_s, target) else raise Scrivito::ClientError.new("Invalid input for binary field", 422) end else raise Scrivito::ClientError.new("Unknown attribute type: #{virtual_type}", 418) end end if (@new_full_text) @obj.set(:X_full_text, @new_full_text) end @obj.set(:X_widget_pool, @new_widget_pool) if @new_widget_pool @obj.set(:permalink, @permalink) if @permalink @obj.save! #if !@values.empty? || !@new_widget_pool.nil? @obj.edit! unless @obj.really_edited? #end end def special_upload_handling(attribute_name, file) if !@obj.binary? # standard handling! @obj.set(attribute_name.to_s, upload_file(file)) else ext = ::File.extname(file.path).to_s[1..-1] @obj.upload(file, ext) end end def special_upload_uploaded_handling(attribute_name, file) if !@obj.binary? # standard handling! @obj.set(attribute_name.to_s, upload_uploaded_file(file)) else ext = ::File.extname(file.original_filename).to_s[1..-1] @obj.upload(file.open, ext) end end def rewrite_full_text full_text = ::YAML.load(@obj.attr_values["X_full_text"]) full_text = {} unless full_text.kind_of?(Hash) full_text["_widget_pool"] ||= {} full_text["_widget_pool"].deep_merge!(@widget_pool) full_text.to_yaml rescue => e Rails.logger.error("Unable to store information for search engine: #{e.message}") nil end def upload_file(file) # only publication type can contain children (code = "5") # also handle / this way if @obj.obj_type_code != "5" || @obj.id == 2001 parent_path = "/_uploads/#{@obj.id}" else parent_path = "#{@obj.path}/_uploads" end parent = ensure_parent_exists(parent_path) ext = ::File.extname(file.path).to_s[1..-1] name = ::File.basename(file.path, '.' + ext.to_s) obj_class = if ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff'].include?(ext) 'X_Image' else 'X_Generic' end raise Scrivito::ScrivitoError.new("File extension of uploaded file cannot be identified", 422) if ext.nil? upload = WriteObj.upload(file, ext, {name: name, parent: parent, obj_class: obj_class}).tap(&:release!) encoded_id = '%032d' % upload.id {title: encoded_id, destination_object: upload} end def upload_uploaded_file(file) # FIXME: DRY # only publication type can contain children (code = "5") # also handle / this way if @obj.obj_type_code != "5" || @obj.id == 2001 parent_path = "/_uploads/#{@obj.id}" else parent_path = "#{@obj.path}/_uploads" end parent = ensure_parent_exists(parent_path) # here the first difference ext = ::File.extname(file.original_filename).to_s[1..-1] name = ::File.basename(file.original_filename, '.' + ext.to_s) obj_class = if ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff'].include?(ext) 'X_Image' else 'X_Generic' end raise Scrivito::ScrivitoError.new("File extension of uploaded file cannot be identified", 422) if ext.nil? # here the second difference upload = WriteObj.upload(file.open, ext, {name: name, parent: parent, obj_class: obj_class}).tap(&:release!) encoded_id = '%032d' % upload.id {title: encoded_id, destination_object: upload} end def name_and_parent_path_from_path(path) components = path.split('/') name = components.pop.presence parent_path= components.join('/').presence || '/' return name, parent_path end def ensure_parent_exists(path) remaining = path.split("/") current = [] paths = [] original = path while !remaining.empty? current.push(remaining.shift) path = current.join('/').presence || '/' paths.push(path) end paths.each do |path| if !WriteObj.exists?(path: path) name, parent_path = name_and_parent_path_from_path(path) WriteObj.create!(name: name, parent_obj_id: WriteObj.find_by_path(parent_path).id, obj_class: 'X_Container') end end WriteObj.find_by_path(original) || (raise "Tried to make sure that the parent under '#{original}' exist, but it does not :(") end def generate_orphaned_path return nil if update? "_orphaned/#{SecureRandom.hex(16)}" end def ensure_obj_class_exists values = @values.with_indifferent_access obj_class = @obj_class Fiona7::TypeRegister.instance.ad_hoc_synchronize( Fiona7::TypeRegister::AdHocTypeDefinition.new(values, obj_class).type_definition ) end end end end