| 
									
										
										
										
											2019-03-16 09:37:35 +00:00
										 |  |  | # frozen_string_literal: true | 
					
						
							| 
									
										
										
										
											2019-03-15 01:32:57 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  | # Redmine - project management software | 
					
						
							| 
									
										
										
										
											2021-03-25 06:58:56 +00:00
										 |  |  | # Copyright (C) 2006-2021  Jean-Philippe Lang | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  | # | 
					
						
							|  |  |  | # This program is free software; you can redistribute it and/or | 
					
						
							|  |  |  | # modify it under the terms of the GNU General Public License | 
					
						
							|  |  |  | # as published by the Free Software Foundation; either version 2 | 
					
						
							|  |  |  | # of the License, or (at your option) any later version. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | # GNU General Public License for more details. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  | # along with this program; if not, write to the Free Software | 
					
						
							|  |  |  | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require 'csv' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ImportsController < ApplicationController | 
					
						
							| 
									
										
										
										
											2016-07-14 07:27:31 +00:00
										 |  |  |   before_action :find_import, :only => [:show, :settings, :mapping, :run] | 
					
						
							| 
									
										
										
										
											2019-05-09 07:40:06 +00:00
										 |  |  |   before_action :authorize_import | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   layout :import_layout | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   helper :issues | 
					
						
							| 
									
										
										
										
											2016-07-12 18:04:01 +00:00
										 |  |  |   helper :queries | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   def new | 
					
						
							| 
									
										
										
										
											2019-05-09 07:40:06 +00:00
										 |  |  |     @import = import_type.new | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def create | 
					
						
							| 
									
										
										
										
											2019-05-09 07:40:06 +00:00
										 |  |  |     @import = import_type.new | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |     @import.user = User.current | 
					
						
							|  |  |  |     @import.file = params[:file] | 
					
						
							| 
									
										
										
										
											2019-12-20 08:07:57 +00:00
										 |  |  |     @import.set_default_settings(:project_id => params[:project_id]) | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if @import.save | 
					
						
							|  |  |  |       redirect_to import_settings_path(@import) | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       render :action => 'new' | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def show | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def settings | 
					
						
							|  |  |  |     if request.post? && @import.parse_file | 
					
						
							| 
									
										
										
										
											2021-06-20 13:25:10 +00:00
										 |  |  |       if @import.total_items == 0
 | 
					
						
							|  |  |  |         flash.now[:error] = l(:error_no_data_in_file) | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         redirect_to import_mapping_path(@import) | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-19 10:11:07 +00:00
										 |  |  |   rescue CSV::MalformedCSVError, EncodingError => e | 
					
						
							| 
									
										
										
										
											2018-05-06 07:48:31 +00:00
										 |  |  |     if e.is_a?(CSV::MalformedCSVError) && e.message !~ /Invalid byte sequence/ | 
					
						
							| 
									
										
										
										
											2021-02-27 07:46:35 +00:00
										 |  |  |       flash.now[:error] = l(:error_invalid_csv_file_or_settings, e.message) | 
					
						
							| 
									
										
										
										
											2018-05-06 07:48:31 +00:00
										 |  |  |     else | 
					
						
							|  |  |  |       flash.now[:error] = l(:error_invalid_file_encoding, :encoding => ERB::Util.h(@import.settings['encoding'])) | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |   rescue SystemCallError => e | 
					
						
							|  |  |  |     flash.now[:error] = l(:error_can_not_read_import_file) | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def mapping | 
					
						
							| 
									
										
										
										
											2016-06-07 18:23:42 +00:00
										 |  |  |     @custom_fields = @import.mappable_custom_fields | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-19 14:52:02 +00:00
										 |  |  |     if request.get? | 
					
						
							|  |  |  |       auto_map_fields | 
					
						
							|  |  |  |     elsif request.post? | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |       respond_to do |format| | 
					
						
							| 
									
										
										
										
											2020-11-06 13:01:00 +00:00
										 |  |  |         format.html do | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |           if params[:previous] | 
					
						
							|  |  |  |             redirect_to import_settings_path(@import) | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             redirect_to import_run_path(@import) | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2020-11-06 13:01:00 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |         format.js # updates mapping form on project or tracker change | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def run | 
					
						
							|  |  |  |     if request.post? | 
					
						
							|  |  |  |       @current = @import.run( | 
					
						
							|  |  |  |         :max_items => max_items_per_request, | 
					
						
							|  |  |  |         :max_time => 10.seconds | 
					
						
							|  |  |  |       ) | 
					
						
							|  |  |  |       respond_to do |format| | 
					
						
							| 
									
										
										
										
											2020-11-06 13:01:00 +00:00
										 |  |  |         format.html do | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |           if @import.finished? | 
					
						
							|  |  |  |             redirect_to import_path(@import) | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             redirect_to import_run_path(@import) | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2020-11-06 13:01:00 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |         format.js | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-09 07:40:06 +00:00
										 |  |  |   def current_menu(project) | 
					
						
							|  |  |  |     if import_layout == 'admin' | 
					
						
							|  |  |  |       nil | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       :application_menu | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |   private | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def find_import | 
					
						
							|  |  |  |     @import = Import.where(:user_id => User.current.id, :filename => params[:id]).first | 
					
						
							|  |  |  |     if @import.nil? | 
					
						
							|  |  |  |       render_404 | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     elsif @import.finished? && action_name != 'show' | 
					
						
							|  |  |  |       redirect_to import_path(@import) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |     update_from_params if request.post? | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def update_from_params | 
					
						
							| 
									
										
										
										
											2017-07-23 11:26:04 +00:00
										 |  |  |     if params[:import_settings].present? | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |       @import.settings ||= {} | 
					
						
							| 
									
										
										
										
											2017-07-23 11:26:04 +00:00
										 |  |  |       @import.settings.merge!(params[:import_settings].to_unsafe_hash) | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  |       @import.save! | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def max_items_per_request | 
					
						
							|  |  |  |     5
 | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2019-05-09 07:40:06 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   def import_layout | 
					
						
							|  |  |  |     import_type && import_type.layout || 'base' | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def menu_items | 
					
						
							|  |  |  |     menu_item = import_type ? import_type.menu_item : nil | 
					
						
							| 
									
										
										
										
											2020-11-14 13:18:56 +00:00
										 |  |  |     {self.controller_name.to_sym => {:actions => {}, :default => menu_item}} | 
					
						
							| 
									
										
										
										
											2019-05-09 07:40:06 +00:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def authorize_import | 
					
						
							|  |  |  |     return render_404 unless import_type | 
					
						
							|  |  |  |     return render_403 unless import_type.authorized?(User.current) | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def import_type | 
					
						
							|  |  |  |     return @import_type if defined? @import_type | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @import_type = | 
					
						
							|  |  |  |       if @import | 
					
						
							|  |  |  |         @import.class | 
					
						
							|  |  |  |       else | 
					
						
							| 
									
										
										
										
											2020-07-08 17:30:12 +00:00
										 |  |  |         type = | 
					
						
							|  |  |  |           begin | 
					
						
							|  |  |  |             Object.const_get(params[:type]) | 
					
						
							|  |  |  |           rescue | 
					
						
							|  |  |  |             nil | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2019-05-09 07:40:06 +00:00
										 |  |  |         type && type < Import ? type : nil | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2020-02-19 14:52:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   def auto_map_fields | 
					
						
							|  |  |  |     # Try to auto map fields only when settings['enconding'] is present | 
					
						
							|  |  |  |     # otherwhise, the import fails for non UTF-8 files because the headers | 
					
						
							|  |  |  |     # cannot be retrieved (Invalid byte sequence in UTF-8) | 
					
						
							|  |  |  |     return if @import.settings['encoding'].blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     mappings = @import.settings['mapping'] ||= {} | 
					
						
							| 
									
										
										
										
											2020-12-05 00:21:15 +00:00
										 |  |  |     headers = @import.headers.map{|header| header&.downcase} | 
					
						
							| 
									
										
										
										
											2020-02-19 14:52:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Core fields | 
					
						
							|  |  |  |     import_type::AUTO_MAPPABLE_FIELDS.each do |field_nm, label_nm| | 
					
						
							|  |  |  |       next if mappings.include?(field_nm) | 
					
						
							| 
									
										
										
										
											2020-11-03 14:55:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-19 14:52:02 +00:00
										 |  |  |       index = headers.index(field_nm) || headers.index(l(label_nm).downcase) | 
					
						
							|  |  |  |       if index | 
					
						
							|  |  |  |         mappings[field_nm] = index | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Custom fields | 
					
						
							|  |  |  |     @custom_fields.each do |field| | 
					
						
							|  |  |  |       field_nm = "cf_#{field.id}" | 
					
						
							|  |  |  |       next if mappings.include?(field_nm) | 
					
						
							| 
									
										
										
										
											2020-11-03 14:55:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-19 14:52:02 +00:00
										 |  |  |       index = headers.index(field_nm) || headers.index(field.name.downcase) | 
					
						
							|  |  |  |       if index | 
					
						
							|  |  |  |         mappings[field_nm] = index | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |     mappings | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2015-08-14 08:20:32 +00:00
										 |  |  | end |