#-- # Copyright (c) 2012+ Damjan Rems # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ require 'sort_alphabetical' ########################################################################### # # DcApplicationHelper defines common helper methods for using with DRG CMS. # ########################################################################### module DcApplicationHelper # page document attr_reader :page # design document attr_reader :design # site document attr_reader :site # menu document attr_reader :menu # selected menu_item document attr_reader :menu_item # tables url parameter attr_reader :tables # ids url parameter attr_reader :ids # form object attr_reader :form # options object attr_reader :options # part attr_reader :part # page title attr_accessor :page_title # all parts read from page, design, ... attr_accessor :parts # attr_accessor :record # attr_accessor :record_footer # json_ld attr_reader :json_ld ############################################################################ # When @parent is present then helper methods are called from parent class otherwise # from self. ############################################################################ def _origin #:nodoc: @parent ? @parent : self end ############################################################################ # Writes out deprication msg. It also adds site_name to message, so it is easier to # find where the message is comming from. ############################################################################ def dc_deprecate(msg) ActiveSupport::Deprecation.warn("#{dc_get_site.name}: #{msg}") end ############################################################################ # This is main method used for render parts of design into final HTML document. # # Parameters: # [renderer] String or Symbol. Class name (in lowercase) that will be used to render final HTML code. # If class name is provided without '_renderer' suffix it will be added automatically. # # When renderer has value :part, it is a shortcut for dc_render_design_part method which # is used to draw parts of layout on design. # # [opts] Hash. Additional options that are passed to method. Options are merged with # options set on site, design, page and passed to renderer object. # # Example: # <%= dc_render(:dc_page, method: 'view', category: 'news') %> ############################################################################ def dc_render(renderer, opts={}) return dc_render_design_part(renderer[:part]) if renderer.class == Hash opts[:edit_mode] = session[:edit_mode] opts[:editparams] = {} opts = @options.merge(opts) # merge options with parameters passed on site, page, design ... opts.symbolize_keys! # this makes lots of things easier # Create renderer object klass = renderer.to_s.downcase klass += '_renderer' unless klass.match('_renderer') # obj = Kernel.const_get(klass.classify, Class.new).new(self, opts) rescue nil if obj html = obj.render_html @css << obj.render_css.to_s html.nil? ? '' : html.html_safe # nil can happened else I18n.t 'drgcms.no_class', class: klass end end ######################################################################## # Used for designs with lots of common code and one (or more) part which differs. # It will simply replace anchor code with value of variable. # # Example: As used in design. Backslashing < and % is important \<\% # <% part = "
\<\%= dc_render(:my_renderer, method: 'render_method') \%\>
" %> # <%= dc_replace_in_design(piece: 'piece_name', replace: '[main]', with: part) %> # # Want to replace more than one part. Use array. # <%= dc_replace_in_design(replace: ['[part1]','[part2]'], with: [part1, part2]) %> # # This helper is replacement for old 'script' method defined in dc_piece_renderer, # but it uses design defined in site document if piece parameter is not set. ######################################################################## def dc_replace_in_design(opts={}) design = opts[:piece] ? DcPiece.find(name: opts[:piece]).script : dc_get_site.design layout = opts[:layout] || (dc_get_site.site_layout.size > 2 ? dc_get_site.site_layout : nil) if opts[:replace] # replace more than one part of code if opts[:replace].class == Array 0.upto(opts[:replace].size - 1) {|i| design.sub!(opts[:replace][i], opts[:with][i])} else design.sub!(opts[:replace], opts[:with]) end end render(inline: design, layout: nil) end ######################################################################## # Used for designs with lots of common code and one (or more) part which differs. # It will simply replace anchor code with value of variable. # # Example: As used in design. Backslashing < and % is important \<\% # <% part = "
\<\%= dc_render(:my_renderer, method: 'render_method') \%\>
" %> # <%= dc_replace_in_design(piece: 'piece_name', replace: '[main]', with: part) %> # # Want to replace more than one part. Use array. # <%= dc_replace_in_design(replace: ['[part1]','[part2]'], with: [part1, part2]) %> # # This helper is replacement for old 'script' method defined in dc_piece_renderer, # but it uses design defined in site document if piece parameter is not set. ######################################################################## def dc_render_from_site(opts={}) design = opts[:piece] ? DcPiece.find(name: opts[:piece]).script : dc_get_site.design layout = opts[:layout] || (dc_get_site.site_layout.size > 2 ? dc_get_site.site_layout : nil) render(inline: design, layout: opts[:layout], with: opts[:with]) end ######################################################################## # Used for designs with lots of common code and one (or more) part which differs. # Point is to define design once and replace some parts of design dinamically. # Design may be defined in site and design doc defines only parts that vary # from page to page. # # Example: As used in design. # <%= dc_render_design_part(@main) %> # # main variable is defined in design body for example: # # @main = Proc.new {render partial: 'parts/home'} # # This helper is replacement dc_render_from_site method which will soon be deprecated. ######################################################################## def dc_render_design_part(part) case when part.nil? then logger.error('ERROR dc_render_design_part! part is NIL !'); '' # Send as array. Part may be defined with options on page. First element has # name of element which defines what to do. If not defined default behaviour is # called. That is what is defined in second part of array. when part.class == Array then if @options.dig(:settings, part.first) #TODO to be defined else result = part.last.call result.class == Array ? result.first : result end when part.class == Proc then result = part.call result.class == Array ? result.first : result # Send as string. Evaluate content of string when part.class == String then eval part # For future maybe. Just call objects to_s method. else part.to_s end.html_safe end def dc_render_design(part) dc_render_design_part(part) end ############################################################################ # This is main method used for render parts of design into final HTML document. # # Parameters: # [renderer] String or Symbol. Class name (in lowercase) that will be used to render final HTML code. # If class name is provided without '_renderer' suffix it will be added automatically. # # When renderer has value :part, it is a shortcut for dc_render_design_part method which # is used to draw parts of layout on design. # # [opts] Hash. Additional options that are passed to method. Options are merged with # options set on site, design, page and passed to renderer object. # # Example: # <%= dc_render(:dc_page, method: 'view', category: 'news') %> ############################################################################ def dc_render_partial(opts={}) _origin.render(partial: opts[:partial], formats: [:html], locals: opts[:locals]) end ######################################################################## # Helper for rendering top CMS menu when in editing mode ######################################################################## def dc_page_top if @design && @design.rails_view.present? # Evaluate parameters in design body eval(@design.body) end session[:edit_mode] > 0 ? render(partial: 'cmsedit/edit_stuff') : '' end ######################################################################## # Helper for adding additional css and javascript code added by documents # and renderers during page rendering. ######################################################################## def dc_page_bottom %(#{javascript_tag @js}).html_safe end ############################################################################ # Creates title div for DRG CMS dialogs. Title may also contain pagination section on right side if # result_set is provided as parameter. # # Parameters: # [text] String. Title caption. # [result_set=nil] Document collection. If result_set is passed pagination links will be created. # # Returns: # String. HTML code for title. ############################################################################ def dc_table_title(text, result_set = nil) c = %(
#{text}) c << dc_help_button(result_set) if result_set&.respond_to?(:current_page) c << %(
#{paginate(result_set, :params => {action: 'index', clear: 'no', filter: nil})}
) end c << '
' c.html_safe end ############################################################################ # Creates title for cmsedit edit action dialog. # # Returns: # String. HTML code for title. ############################################################################ def dc_edit_title session[:form_processing] = "form:title:" title_data = @form['form']['title'] if title_data.class == String t(title_data, title_data) # defined as form:title:edit elsif title_data&.dig('edit') && !@form['readonly'] t( title_data['edit'], title_data['edit'] ) elsif title_data&.dig('show') && @form['readonly'] t( title_data['show'], title_data['show'] ) else c = (@form['readonly'] ? t('drgcms.show') : t('drgcms.edit')) + " : " c << (@form['title'].class == String ? t( @form['title'], @form['title'] ) : t_tablename(@form['table'])) # add description field value to title field_name = title_data['field'] if title_data c << " : #{@record[ field_name ]}" if field_name && @record.respond_to?(field_name) c end end ############################################################################ # Creates title for cmsedit new action dialog. # # Returns: # String. HTML code for title. ############################################################################ def dc_new_title session[:form_processing] = "form:title:" title_data = @form['form']['title'] if title_data.class == String t(title_data, title_data) # defined as form:title:new elsif title_data&.dig('new') t( title_data['new'], title_data['new'] ) else # in memory structures if @form['table'] == 'dc_memory' return t( @form['title'], @form['title'] ) if @form['title'] t("#{@form['i18n_prefix']}.tabletitle", '') else "#{t('drgcms.new')} : #{t_tablename(@form['table'])}" end end end ############################################################################ # Similar to rails submit_tag, but also takes care of link icon, translation, ... ############################################################################ def dc_submit_tag(caption, icon, parms, rest = {}) icon_image = dc_icon_for_link(icon, nil) %().html_safe end ############################################################################ # Returns icon code if icon is specified ############################################################################ def dc_icon_for_link(icon, clas = 'dc-link-img') return '' if icon.blank? if icon.match(/\./) _origin.image_tag(icon, class: clas) elsif icon.match('#{err}" if err html << "
#{war}
" if war html << "
#{inf}
" if inf html << note if note _origin.flash[:error] = nil _origin.flash[:warning] = nil _origin.flash[:info] = nil _origin.flash[:note] = nil end # Update fields on the form if _origin.flash[:update] html << "
\n" _origin.flash[:update].each do |field, value| html << %Q[
\n] end html << '
' _origin.flash[:update] = nil end html.html_safe end ######################################################################## # Decamelizes string. This probably doesn't work very good with non ascii chars. # Therefore it is very unwise to use non ascii chars for table (collection) names. # # Parameters: # [Object] model_string. String or model to be converted into decamelized string. # # Returns: # String. Decamelized string. ######################################################################## def decamelize_type(model_string) model_string ? model_string.to_s.underscore : nil end #################################################################### # Returns validation error messages for the document (record) formatted for # display on message div. # # Parameters: # [doc] Document. Document record which will be checked for errors. # # Returns: # String. HTML code formatted for display. #################################################################### def dc_error_messages_for(doc) return '' unless doc && doc.errors.any? msgs = doc.errors.inject('') do |r, error| label = t("helpers.label.#{decamelize_type(doc.class)}.#{error.attribute}", error.attribute) r << "
  • #{label} : #{error.message}
  • " end %(

    #{t('drgcms.errors_no')} #{doc.errors.size}

    ).html_safe end #################################################################### # Returns warning messages if any set in a model. # # When warnings array is added to model its content can be written on top of the form. # # Parameters: # [doc] Document. Document record which will be checked for errors. # # Returns: # String. HTML code formatted for display. #################################################################### def dc_warning_messages_for(doc) return '' return '' unless doc && doc.respond_to?(:warnings) msgs = doc.warnings.inject('') do |r, error| label = t("helpers.label.#{decamelize_type(doc.class)}.#{error.attribute}", error.attribute) msgs << "
  • #{label} : #{error.message}
  • " end %(

    #{t('drgcms.warnings_no')} #{doc.warnings.size}

    ).html_safe end #################################################################### # Checks if CMS is in edit mode (CMS menu bar is visible). # # Returns: # Boolean. True if in edit mode #################################################################### def dc_edit_mode? _origin.session[:edit_mode] > 1 end #################################################################### # Will create HTML code required to create new document. # # Parameters: # [opts] Hash. Optional parameters for url_for helper. These options must provide at least table and form_name # parameters. # # Example: # if @opts[:edit_mode] > 1 # opts = {table: 'dc_page;dc_part', form_name: 'dc_part', ids: @doc.id } # html << dc_link_for_create( opts.merge!({title: 'Add new part', 'dc_part.name' => 'initial name', 'dc_part.order' => 10}) ) # end # # Returns: # String. HTML code which includes add image and javascript to invoke new document create action. #################################################################### def dc_link_for_create(opts) opts.stringify_keys! title = opts.delete('title') # title = t(title, title) if title target = opts.delete('target') || 'iframe_cms' opts['form_name'] ||= opts['table'].to_s.split(';').last opts['action'] = 'new' opts['controller'] ||= 'cmsedit' js = "$('##{target}').attr('src', '#{_origin.url_for(opts)}'); return false;" dc_link_to(nil, _origin.fa_icon('plus-circle'), '#', { onclick: js, title: title, alt: 'Create', class: 'dc-inline-link'}).html_safe end #################################################################### # Will create HTML code required to edit document. # # Parameters: # [opts] Hash. Optional parameters for url_for helper. These options must provide # at least table, form_name and id parameters. Optional title, target and icon parameters # can be set. # # Example: # html << dc_link_for_edit( @options ) if @opts[:edit_mode] > 1 # # Returns: # String. HTML code which includes edit image and javascript to invoke edit document action. #################################################################### def dc_link_for_edit(opts) opts.stringify_keys! title = opts.delete('title') # title = t(title) target = opts.delete('target') || 'iframe_cms' icon = opts.delete('icon') || 'edit-o' opts['controller'] ||= 'cmsedit' opts['action'] ||= 'edit' opts['form_name'] ||= opts['table'].to_s.split(';').last js = "$('##{target}').attr('src', '#{_origin.url_for(opts)}'); return false;" dc_link_to(nil, _origin.fa_icon(icon), '#', { onclick: js, title: title, class: 'dc-inline-link', alt: 'Edit'}) end #################################################################### # Create edit link with edit picture. Subroutine of dc_page_edit_menu. #################################################################### def dc_link_menu_tag(title) #:nodoc: html = %(
      ) yield html html << "
    " end #################################################################### # Create one option in page edit link. Subroutine of dc_page_edit_menu. #################################################################### def dc_link_for_edit1(opts, link_text) #:nodoc: icon = opts.delete('icon') url = _origin.url_for(opts) "
  • #{_origin.fa_icon(icon)} #{link_text}
  • \n" end ######################################################################## # Create edit menu for editing existing or creating new dc_page documents. Edit menu # consists of for options. # * Edit content. Will edit only body part od document. # * Edit advanced. Will create edit form for editing all document fields. # * New page. Will create new document and pass some initial data to it. Initial data is saved to cookie. # * New part. Will create new part of document. # # Parameters: # [opts] Hash. Optional parameters for url_for helper. These options must provide at least table and form_name # and id parameters. # # Example: # html << dc_page_edit_menu() if @opts[:edit_mode] > 1 # # Returns: # String. HTML code required for manipulation of currently processed document. ######################################################################## def dc_page_edit_menu(opts = @opts) opts[:edit_mode] ||= _origin.session[:edit_mode] return '' if opts[:edit_mode] < 2 # save some data to cookie. This can not go to session. page = opts[:page] || @page table = _origin.site.page_class.underscore kukis = { "#{table}.dc_design_id" => page.dc_design_id, # "#{table}.menu_id" => page.menu_id, "#{table}.kats" => page.kats, "#{table}.page_id" => page.id, "#{table}.dc_site_id" => _origin.site.id } _origin.cookies[:record] = Marshal.dump(kukis) title = "#{t('drgcms.edit')}: #{page.subject}" opts[:editparams] ||= {} dc_link_menu_tag(title) do |html| opts[:editparams].merge!( controller: 'cmsedit', action: 'edit', 'icon' => 'edit-o' ) opts[:editparams].merge!( :id => page.id, :table => _origin.site.page_class.underscore, form_name: opts[:form_name], edit_only: 'body' ) html << dc_link_for_edit1( opts[:editparams], t('drgcms.edit_content') ) opts[:editparams].merge!( edit_only: nil, 'icon' => 'edit-o' ) html << dc_link_for_edit1( opts[:editparams], t('drgcms.edit_advanced') ) opts[:editparams].merge!( action: 'new', 'icon' => 'plus' ) html << dc_link_for_edit1( opts[:editparams], t('drgcms.edit_new_page') ) opts[:editparams].merge!(ids: page.id, form_name: 'dc_part', 'icon' => 'plus', table: "#{_origin.site.page_class.underscore};dc_part" ) html << dc_link_for_edit1( opts[:editparams], t('drgcms.edit_new_part') ) end.html_safe end ######################################################################## # Return page class model defined in site document page_class field. # # Used in forms, when method must be called from page model and model is overwritten by # user's own model. # # Example as used on form: # 30: # name: link # type: text_with_select # eval: 'dc_page_class.all_pages_for_site(@parent.dc_get_site)' ######################################################################## def dc_page_class dc_get_site.page_klass end ######################################################################## # Return menu class model defined in site document menu_class field. # # Used in forms for providing menus class to the forms object. # # Example as used on form: # 30: # name: menu_id # type: tree_view # eval: 'dc_menu_class.all_menus_for_site(@parent.dc_get_site)' ######################################################################## def dc_menu_class dc_get_site.menu_class.classify.constantize end #################################################################### # Parse site name from url and return dc_site document. Site document will be cached in # @site variable. # # If not in production environment and site document is not found # method will search for 'test' document and return dc_site document found in alias_for field. # # Returns: # DCSite. Site document. #################################################################### def dc_get_site return @site if @site # already cached req = _origin.request.url # different when called from renderer uri = URI.parse(req) @site = DcSite.find_by(name: uri.host) # Site can be aliased @site = DcSite.find_by(name: @site.alias_for) if @site&.alias_for.present? # Development. If site with name test exists use alias_for field as pointer to real site data if @site.nil? && ENV["RAILS_ENV"] != 'production' @site = DcSite.find_by(name: 'test') @site = DcSite.find_by(name: @site.alias_for) if @site end @site = nil if @site && !@site.active # site is disabled @site end ############################################################################ # Return array of policies defined in a site document formated to be used # as choices for select field. Method is used for selecting site policy where # policy for displaying data is required. # # Example (as used in forms): # name: policy_id # type: select # eval: dc_choices4_site_policies # html: # include_blank: true ############################################################################ def dc_choices4_site_policies site = dc_get_site() site.dc_policies.where(active: true).order_by(name: 1).map { |policy| [ policy.name, policy.id] } end ############################################################################ # Returns list of all collections (tables) as array of choices for usage in select fields. # List is collected from cms_menu.yml files and may not include all collections used in application. # Currently list is only used for helping defining collection names on dc_permission form. # # Example (as used in forms): # form: # fields: # 10: # name: table_name # type: text_with_select # eval: dc_choices4_all_collections ############################################################################ def dc_choices4_all_collections models = Mongoid.models.map(&:to_s).uniq.map(&:underscore).delete_if { |e| e.match('/') } models.sort.inject([]) do |r, model_name| r << ["#{model_name} - #{t("helpers.label.#{model_name}.tabletitle", '')}", model_name] end end ########################################################################## # Code for top CMS menu. ########################################################################## def dc_cms_menu menus = {} DrgCms.paths(:forms).reverse.each do |path| filename = "#{path}/cms_menu.yml" next if !File.exist?(filename) menu = YAML.load_file(filename) rescue nil # load menu menus = CmsHelper.forms_merge(menu['menu'], menus) if menu.dig('menu') # merge menus end html = '