Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions app/controllers/admin/imports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

# Copyright (c) 2008-2013 Michael Dvorkin and contributors.
#
# Fat Free CRM is freely distributable under the terms of MIT license.
# See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------
class Admin::ImportsController < Admin::ApplicationController
before_action :setup_current_tab, only: %i[index import]

# GET /admin/imports
#----------------------------------------------------------------------------
def index
end

# POST /admin/imports/import
#----------------------------------------------------------------------------
def import
if params[:file].nil?
flash[:error] = t(:msg_no_file_chosen)
redirect_to admin_imports_path and return
end

klass = params[:klass].classify.constantize rescue nil

Check failure

Code scanning / Brakeman

Unsafe reflection method constantize called on parameter value. Error

Unsafe reflection method constantize called on parameter value.

Check failure

Code scanning / CodeQL

Code injection Critical

This code execution depends on a
user-provided value
.

Check notice

Code scanning / Rubocop

Avoid using rescue in its modifier form. Note

Style/RescueModifier: Avoid using rescue in its modifier form.
unless [Lead, Account, Campaign, Opportunity, Contact, Task].include?(klass)
flash[:error] = t(:msg_invalid_class)
redirect_to admin_imports_path and return
end

import_records(klass, params[:file])
redirect_to admin_imports_path
end

private

def import_records(klass, file)
require 'csv'
@imported_count = 0
@errors = []

CSV.foreach(file.path, headers: true) do |row|
attributes = row.to_hash
mapped_attributes = map_attributes(klass, attributes)

record = klass.new(mapped_attributes)
record.user = current_user
record.access = Setting.default_access if record.respond_to?(:access=)

if record.save
@imported_count += 1
else
@errors << "#{row.to_s.strip}: #{record.errors.full_messages.join(', ')}"
end
end

if @errors.empty?
flash[:notice] = t(:msg_imported_records, count: @imported_count, records: klass.model_name.human(count: @imported_count).downcase)
else
flash[:warning] = t(:msg_imported_records_with_errors, count: @imported_count, records: klass.model_name.human(count: @imported_count).downcase, errors: @errors.size)
flash[:error] = @errors.join("<br/>").html_safe

Check notice

Code scanning / Rubocop

The use of `html_safe` or `raw` may be a security risk. Note

Rails/OutputSafety: Tagging a string as html safe may be a security risk.
end
end

def map_attributes(klass, attributes)
mapped = {}
attributes.each do |key, value|
next if value.blank?

attr_name = find_attribute_name(klass, key)
mapped[attr_name] = value if attr_name
end
mapped
end

def find_attribute_name(klass, key)
key = key.to_s.strip
return key if klass.column_names.include?(key)

normalized_key = key.downcase.gsub(' ', '_')
return normalized_key if klass.column_names.include?(normalized_key)

klass.column_names.each do |col|
return col if klass.human_attribute_name(col).downcase == key.downcase
end

nil
end

def setup_current_tab
set_current_tab('admin/imports')
end
end
86 changes: 0 additions & 86 deletions app/controllers/admin/leads_controller.rb

This file was deleted.

27 changes: 27 additions & 0 deletions app/views/admin/imports/index.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.title
= t(:admin_tab_import)

.remote
= form_tag import_admin_imports_path, multipart: true do
.section
.subtitle
= t(:import_help)
%p
= t(:import_supported_headers)
%p
%strong= t(:import_example_header)
%pre
= t(:import_example_content)
%p
%em= t(:import_note)
%table
%tr
%td
.label.top.req #{t(:import_type)}:
= select_tag :klass, options_for_select([[t(:lead), 'Lead'], [t(:account), 'Account'], [t(:campaign), 'Campaign'], [t(:opportunity), 'Opportunity'], [t(:contact), 'Contact'], [t(:task), 'Task']])
%tr
%td
.label.top.req #{t(:csv_file)}:
= file_field_tag :file
.buttonbar
= submit_tag t(:import_records_button), id: :import_submit
23 changes: 0 additions & 23 deletions app/views/admin/leads/index.html.haml

This file was deleted.

19 changes: 11 additions & 8 deletions config/locales/fat_free_crm.en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ en-US:
tab_opportunities: Opportunities
tab_team: Team
admin_tab_groups: Groups
admin_tab_leads_import: "Leads Import"
admin_tab_import: "Import"
admin_tab_users: Users
admin_tab_fields: Custom Fields
admin_tab_tags: Tags
Expand Down Expand Up @@ -203,8 +203,9 @@ en-US:
msg_asset_deleted: ! '%{value} has been deleted.'
msg_asset_not_available: This %{value} is no longer available.
msg_no_file_chosen: "Please choose a file to upload."
msg_imported_leads: "Successfully imported %{count} leads."
msg_imported_leads_with_errors: "Imported %{count} leads, but %{errors} records failed."
msg_invalid_class: "Invalid record type selected for import."
msg_imported_records: "Successfully imported %{count} %{records}."
msg_imported_records_with_errors: "Imported %{count} %{records}, but %{errors} records failed."
msg_not_authorized: You are not authorized to take this action.
msg_assets_not_available: The %{value} are not available.
msg_asset_rejected: ! '%{value} has been rejected.'
Expand Down Expand Up @@ -464,11 +465,13 @@ en-US:
create_lead: Create Lead
import_leads: Import Leads
csv_file: CSV File
leads_import_help: "Upload a CSV file to import leads. The first row should contain the headers. The importer will automatically map headers to lead attributes."
leads_import_supported_headers: "Supported headers include: First Name, Last Name, Email, Company, Title, Phone, Mobile, Source, Status, Referred By, Rating, Do Not Call, and Background Info."
leads_import_example_header: "Example CSV format:"
leads_import_example_content: "First Name,Last Name,Email,Company\nJohn,Doe,john.doe@example.com,ACME Corp\nJane,Smith,jane.smith@example.com,Global Industries"
leads_import_note: "Note: Custom fields can also be imported using their labels or column names (e.g., cf_custom_field)."
import_help: "Upload a CSV file to import leads. The first row should contain the headers. The importer will automatically map headers to lead attributes."
import_supported_headers: "Supported headers include: First Name, Last Name, Email, Company, Title, Phone, Mobile, Source, Status, Referred By, Rating, Do Not Call, and Background Info."
import_example_header: "Example CSV format:"
import_example_content: "First Name,Last Name,Email,Company\nJohn,Doe,john.doe@example.com,ACME Corp\nJane,Smith,jane.smith@example.com,Global Industries"
import_note: "Note: Custom fields can also be imported using their labels or column names (e.g., cf_custom_field)."
import_type: "Import Type"
import_records_button: "Import Records"
create_opp_for_contact: You can optionally create an opportunity for the %{value}
contact by specifying the name, current stage, estimated closing date, sale probability,
amount of the deal, and the discount offered.
Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
end

namespace :admin do
resources :leads, only: [:index] do
resources :imports, only: [:index] do
collection do
post :import
end
Expand Down
4 changes: 2 additions & 2 deletions config/settings.default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,10 @@
:url:
:controller: admin/users
- :active: false
:text: :admin_tab_leads_import
:text: :admin_tab_import
:icon: :fa-upload
:url:
:controller: admin/leads
:controller: admin/imports
- :active: true
:text: :admin_tab_groups
:icon: :fa-users
Expand Down
25 changes: 25 additions & 0 deletions server.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
bundler: failed to load command: ./bin/rails (./bin/rails)
/app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/definition.rb:524:in `materialize': Could not find rails-8.0.5, rails-i18n-8.1.0, responders-3.2.0, jquery-rails-4.6.1, font-awesome-rails-4.7.0.9, rails3-jquery-autocomplete-1.0.15, rails_autolink-1.1.8, rspec-rails-7.1.1, factory_bot_rails-6.5.1, sassc-rails-2.1.2, coffee-rails-5.0.0, devise-5.0.3, devise-i18n-1.15.0, devise-encryptable-0.2.0, devise-security-0.18.0, bootstrap-5.2.3, solid_queue-1.4.0, railties-8.0.5, irb-1.17.0, rdoc-7.2.0, psych-5.3.1 in locally installed gems (Bundler::GemNotFound)
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/definition.rb:197:in `specs'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/definition.rb:254:in `specs_for'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/runtime.rb:18:in `setup'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler.rb:171:in `setup'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/setup.rb:23:in `block in <top (required)>'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/ui/shell.rb:159:in `with_level'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/ui/shell.rb:111:in `silence'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/setup.rb:23:in `<top (required)>'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/cli/exec.rb:56:in `require_relative'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/cli/exec.rb:56:in `kernel_load'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/cli/exec.rb:23:in `run'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/cli.rb:492:in `exec'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/cli.rb:34:in `dispatch'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/cli.rb:28:in `start'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/exe/bundle:45:in `block in <top (required)>'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/lib/bundler/friendly_errors.rb:117:in `with_friendly_errors'
from /app/vendor/bundle/ruby/3.2.0/gems/bundler-2.4.10/exe/bundle:33:in `<top (required)>'
from /usr/local/bin/bundle:25:in `load'
from /usr/local/bin/bundle:25:in `<main>'
Loading
Loading