# frozen_string_literal: truemoduleActiveAdmin# CSVBuilder stores CSV configuration## Usage example:## csv_builder = CSVBuilder.new# csv_builder.column :id# csv_builder.column("Name") { |resource| resource.full_name }# csv_builder.column(:name, humanize_name: false)# csv_builder.column("name", humanize_name: false) { |resource| resource.full_name }## csv_builder = CSVBuilder.new col_sep: ";"# csv_builder = CSVBuilder.new humanize_name: false# csv_builder.column :id##classCSVBuilder# Return a default CSVBuilder for a resource# The CSVBuilder's columns would be Id followed by this# resource's content columnsdefself.default_for_resource(resource)newresource: resourcedocolumn:idresource.content_columns.each{|c|columnc}endendattr_reader:columns,:options,:view_contextCOLUMN_TRANSITIVE_OPTIONS=[:humanize_name].freezedefinitialize(options={},&block)@resource=options.delete(:resource)@columns=[]@options=ActiveAdmin.application.csv_options.mergeoptions@block=blockenddefcolumn(name,options={},&block)@columns<<Column.new(name,@resource,column_transitive_options.merge(options),block)enddefbuild(controller,csv)columns=exec_columnscontroller.view_contextbom=options[:byte_order_mark]column_names=options.delete(:column_names){true}csv_options=options.except:encoding_options,:humanize_name,:byte_order_markcsv<<bomifbomifcolumn_namescsv<<CSV.generate_line(columns.map{|c|sanitize(encode(c.name,options))},**csv_options)endcontroller.send(:in_paginated_batches)do|resource|csv<<CSV.generate_line(build_row(resource,columns,options),**csv_options)endcsvenddefexec_columns(view_context=nil)@view_context=view_context@columns=[]# we want to re-render these every instanceinstance_exec&@blockif@block.present?columnsenddefbuild_row(resource,columns,options)columns.mapdo|column|sanitize(encode(call_method_or_proc_on(resource,column.data),options))endenddefencode(content,options)ifoptions[:encoding]ifoptions[:encoding_options]content.to_s.encodeoptions[:encoding],**options[:encoding_options]elsecontent.to_s.encodeoptions[:encoding]endelsecontentendenddefsanitize(content)Sanitizer.sanitize(content)enddefmethod_missing(method,*args,&block)if@view_context.respond_to?method@view_context.public_sendmethod,*args,&blockelsesuperendendclassColumnattr_reader:name,:data,:optionsDEFAULT_OPTIONS={humanize_name: true}definitialize(name,resource=nil,options={},block=nil)@options=options.reverse_merge(DEFAULT_OPTIONS)@name=humanize_name(name,resource,@options[:humanize_name])@data=block||name.to_symenddefhumanize_name(name,resource,humanize_name_option)ifhumanize_name_optionname.is_a?(Symbol)&&resource?resource.resource_class.human_attribute_name(name):name.to_s.humanizeelsename.to_sendendendprivatedefcolumn_transitive_options@column_transitive_options||=@options.slice(*COLUMN_TRANSITIVE_OPTIONS)endend# Prevents CSV Injection according to https://owasp.org/www-community/attacks/CSV_InjectionmoduleSanitizerextendselfATTACK_CHARACTERS=['=','+','-','@',"\t","\r"].freezedefsanitize(value)return"'#{value}"ifrequire_sanitization?(value)valueenddefrequire_sanitization?(value)value.is_a?(String)&&value.starts_with?(*ATTACK_CHARACTERS)endendend