module RailsConnector module AttributeContent extend ActiveSupport::Concern included do private attr_accessor :data_from_cms end def respond_to?(method_id, include_private=false) if has_attribute?(method_id) true else super end end def method_missing(method_name, *args) if has_attribute?(method_name) read_attribute(method_name.to_s) else super end end def widgets(attribute_name) unless (type_of_attribute(attribute_name) == 'widget') raise "Not a widget field: #{attribute_name}." end widgets = read_attribute(attribute_name.to_s, false) return [] unless widgets.present? output_objs = [] (widgets['layout'] || []).each do |widget_hash| begin widget_obj = self.class.find(widget_hash['widget']) rescue RailsConnector::ResourceNotFound => e end if widget_obj.present? && widget_obj.path.start_with?("/_widgets/#{id}/") output_objs << widget_obj end end output_objs end def read_attribute(attribute_name, skip_widgets = true) if skip_widgets && (type_of_attribute(attribute_name) == 'widget') raise "Field #{attribute_name} not (yet) available, since it's a widget" end @attribute_cache.fetch(attribute_name) do (raw_value, attribute_type) = data_from_cms.value_and_type_of(attribute_name) @attribute_cache[attribute_name] = prepare_attribute_value(raw_value, attribute_type) end end def has_attribute?(name) data_from_cms.has_custom_attribute?(name.to_s) end # @return [String] def type_of_attribute(field_name) data_from_cms.type_of(field_name.to_s) end # Returns the value of an internal or external attribute specified by its name. # Passing an invalid key will not raise an error, but return +nil+. # @api public def [](key) key = key.to_s has_attribute?(key) ? read_attribute(key) : nil end # Hook method to control which widget classes should be avaiable for this page. # Override it to allow only certain classes or none. # Must return either {NilClass}, or {Array}. # # If {NilClass} is returned, then all widget classes will be available for this page. # By default {NilClass} is returned. # # If {Array} is returned, then it should include desired class names. # Each class name must be either a {String} or a {Symbol}. # Only this class names will be available for this page. # Order of the class names will be preserved. # # @param [String] field_name Name of the widget field. # @return [NilClass, Array] # @api public def valid_widget_classes_for(field_name) end private def update_data(data) self.data_from_cms = data @attribute_cache = {} end def prepare_attribute_value(attribute_value, attribute_type) case attribute_type when "html" StringTagging.tag_as_html(convert_links(attribute_value), self) when "date" DateAttribute.parse(attribute_value) if attribute_value when "linklist" build_links(attribute_value) when "reference" self.class.find([attribute_value]).first when "referencelist" self.class.find(attribute_value).compact else attribute_value end end def convert_links(html) if html html.gsub(%r{?(['"]?)}) do link, postfix = text_link_map[$1], $2 link ? convert_link(link, postfix) : missing_link_url(postfix) end end end def convert_link(link, postfix) link.internal? ? convert_internal_link(link, postfix) : convert_external_link(link, postfix) end def convert_internal_link(link, postfix) link.obj ? decorate_link_url(link.internal_url, link, postfix) : missing_link_url(postfix) end def convert_external_link(link, postfix) decorate_link_url(link.external_url, link, postfix) end def decorate_link_url(url, link, postfix) "#{url}#{link.query_and_fragment}#{postfix} #{link.html_attribute_snippet}" end def missing_link_url(postfix) "#{CmsRoutingHelper::LINK_TO_UNREACHABLE}#{postfix}" end def text_link_map @_text_link_map ||= text_links.each_with_object({}) { |link, map| map[link.id] = link } end def build_links(link_definitions) if link_definitions.present? link_definitions = link_definitions.map(&:with_indifferent_access) object_ids = link_definitions.map { |link_data| link_data[:destination] }.compact.uniq objects = object_ids.empty? ? [] : Obj.find(object_ids) link_definitions.each_with_object([]) do |link_data, links| obj = objects.detect { |o| o && o.id == link_data[:destination] } link = Link.new(link_data.merge(obj: obj)) links << link if link.active? end else [] end end module ClassMethods # instantiate an Obj instance from obj_data. # May result in an instance of a subclass of Obj according to STI rules. # # A CMS administrator can specify the obj_class for a given CMS object. # In Rails, this could be either: # # * A valid and existing model name # * A valid and non-existing model name # * An invalid model name # # Rails' STI mechanism only considers the first case. # In any other case, RailsConnector::Obj is used, except when explicitly asked # for a model in the RailsConnector namespace (RailsConnector::Permission etc.) def instantiate(obj_data) obj_class = obj_data.value_of('_obj_class') type_computer.compute_type(obj_class).new(obj_data) end end end end