file-manager/app/controllers/admin/file_managers_controller.rb

500 lines
18 KiB
Ruby

class Admin::FileManagersController < OrbitAdminController
include ActionView::Helpers::NumberHelper
before_action :set_base_url, except: [:settings, :update_settings]
def render_403
render :file => "#{Rails.root}/app/views/errors/403.html", :layout => false, :status => 403, :formats => [:html]
end
def render_404
render :file => "#{Rails.root}/app/views/errors/404.html", :layout => false, :status => 404, :formats => [:html]
end
def forbidden_error
render :body => nil, :status => 403
end
def index
end
def module
@current_user = current_user
@current_user_id = @current_user.id
@recycle_bin = params[:recycle_bin] == 'true'
@file = nil
if params[:file_id]
upload_item = FileManagerUpload.where(:id=> params[:file_id]).first
@upload_item = upload_item
if upload_item
@active_version = @upload_item.version
@id = @upload_item.id
@is_img = !(@upload_item.filename.match(/\.(jpg|gif|png|webp)$/i).nil?)
if upload_item.is_trash
absolute_path = upload_item.file_manager_trash.trash_path
else
absolute_path = upload_item.get_real_path
end
@all_uploads = get_uploads(upload_item.path, true, {:is_trash=>false})
if File.file?(absolute_path)
if params[:download]
send_file(absolute_path)
elsif File.size(absolute_path) > 1_000_000
@file = "File is too big!"
@too_big = true
else
@file = File.read(absolute_path)
@file = fix_encoding(@file)
end
end
end
end
if @recycle_bin
recycle_bin
else
check_path_exist('')
populate_directory(@module_key, '')
end
if request.xhr?
if @file
render :partial => "file"
elsif @recycle_bin
render :partial => "recycle_bin"
else
render :partial => "module"
end
end
end
def recycle_bin
@thread = Multithread.where(:key=>"clean_trashes_#{@current_user_id}").first
@thread_title = nil
if @thread && @thread.status[:status] == 'finish'
@thread = nil
elsif @thread
@thread_title = I18n.t("file_manager.clean_up_recycle_bin")
end
if @thread.nil?
@thread = Multithread.where(:key=>"recover_all_#{@current_user_id}").first
if @thread && @thread.status[:status] == 'finish'
@thread = nil
elsif @thread
@thread_title = I18n.t("file_manager.recover_all")
end
end
@relative_path = I18n.t("file_manager.recycle_bin")
# @relative_to_pwd = @root_path
query_hash = {is_slave: false}
if @only_editable_for_uploader
query_hash[:user_id] = @current_user_id
end
trashes = get_trashes(query_hash).page(params[:page]).per(10)
@trashes = trashes
@module_info_dict = trashes.map{|trash| trash.module}.uniq.map{|k| [k, FileManagerSetting.get_module_info(k)]}.to_h
@directory = trashes.map do |record|
file = record.trash_path
stat = File.stat(file) rescue nil
next if stat.nil?
is_file = record.is_file
real_path_absolute = File.expand_path(file)
file = file.sub(/^#{Regexp.escape(@root_path)}/,'/')
entry = "#{file}#{is_file ? '': '/'}"
version = (record.record_only ? record.get_version : nil)
org_path = record.path.sub(/^#{Regexp.escape(@root_path)}/,'')
file_name = File.basename(file)
if version
org_path += " (version #{version})"
file_name += " (version #{version})"
end
# is_editable = @default_editable || check_editable("#{@relative_to_pwd}/#{entry}", @current_user_id)
{
size: (is_file ? (number_to_human_size stat.size rescue '-'): '-'),
type: (is_file ? :file : :directory),
date: (stat.mtime.strftime(@format_time) rescue '-'),
relative: my_escape(file).gsub('%2F', '/'),
entry: entry,
absolute: real_path_absolute,
org_path: org_path,
deleted_at: (record.created_at.strftime(@format_time) rescue '-'),
name: file_name,
module: record.module,
id: record.id.to_s,
upload_id: record.file_manager_upload_id.to_s,
version: (record.record_only ? record.get_version : nil)
# is_editable: is_editable
}
end.compact
end
def recover_all
@current_user_id = current_user.id
thread = Multithread.where(:key=>"recover_all_#{@current_user_id}").first
if thread.nil?
thread = Multithread.create(:key=>"recover_all_#{@current_user_id}",:status=>{:status=>'Processing'})
else
thread.update(:status=>{:status=>'Processing'})
end
Thread.new do
query_hash = {is_slave: false}
if @only_editable_for_uploader
query_hash[:user_id] = @current_user_id
end
trashes = get_trashes(query_hash)
all_count = trashes.count
puts_every_count = [all_count * 3 / 100, 1].max
current_count = 0
finish_percent = 0
thread.update(:status=>{:status=>'Recovering','all_count'=>all_count,'current_count'=>current_count,'finish_percent'=>finish_percent})
trashes.each do |trash|
trash.recover_file
current_count += 1
if current_count % puts_every_count == 0
finish_percent = (current_count * 100.0 / all_count).round(1)
thread.update(:status=>{:status=>'Recovering','all_count'=>all_count,'current_count'=>current_count,'finish_percent'=>finish_percent})
end
end
thread.update(:status=>{:status=>'finish','all_count'=>all_count,'current_count'=>all_count,'finish_percent'=>100})
end
render :json => {id: thread.id.to_s, title: I18n.t("file_manager.recover_all")}
end
def clean_trashes
@current_user_id = current_user.id
thread = Multithread.where(:key=>"clean_trashes_#{@current_user_id}").first
if thread.nil?
thread = Multithread.create(:key=>"clean_trashes_#{@current_user_id}",:status=>{:status=>'Processing'})
else
thread.update(:status=>{:status=>'Processing'})
end
Thread.new do
query_hash = {is_slave: false}
if @only_editable_for_uploader
query_hash[:user_id] = @current_user_id
end
trashes = get_trashes(query_hash)
all_count = trashes.count
puts_every_count = [all_count * 3 / 100, 1].max
current_count = 0
finish_percent = 0
thread.update(:status=>{:status=>'Deleting','all_count'=>all_count,'current_count'=>current_count,'finish_percent'=>finish_percent})
trashes.each do |trash|
trash.delete_file
current_count += 1
if current_count % puts_every_count == 0
finish_percent = (current_count * 100.0 / all_count).round(1)
thread.update(:status=>{:status=>'Deleting','all_count'=>all_count,'current_count'=>current_count,'finish_percent'=>finish_percent})
end
end
thread.update(:status=>{:status=>'finish','all_count'=>all_count,'current_count'=>all_count,'finish_percent'=>100})
end
render :json => {id: thread.id.to_s, title: I18n.t("file_manager.clean_up_recycle_bin")}
end
def get_trash_count
@current_user_id = current_user.id
query_hash = {is_slave: false}
if @only_editable_for_uploader
query_hash[:user_id] = @current_user_id
end
trashes = get_trashes(query_hash)
render :json => {count: trashes.count}
end
def destroy
if params[:permanent] == 'true'
if params[:type] == 'upload'
upload_record = FileManagerUpload.where(:id=>params[:id]).first
upload_record.delete_file_permanent(true)
else
trash = FileManagerTrash.where(:id=>params[:id]).first
trash.delete_file
end
render :body => nil, :status => 204
else
upload_record = FileManagerUpload.where(:id=>params[:id]).first
if params[:type] == 'upload'
newest_record = upload_record.delete_file(true)
if newest_record
data = {'id': newest_record.id.to_s, 'version': newest_record.version}
else
data = {}
end
render :json => data and return
else
absolute_path = upload_record.path
if FileManagerUpload.check_restricted(absolute_path)
forbidden_error
else
relative_path = absolute_path
is_file = !(File.directory?(absolute_path))
unless is_file
relative_path += '/'
end
upload_records = get_uploads(relative_path, is_file, {:is_default=>true})
if is_file
upload_records.each{|record| record.delete_file(false,current_user.id, params[:force_delete] == 'true') }
else
has_matched = false
master_record_idx = upload_records.index{|record| record.path == relative_path}
master_record = upload_records[master_record_idx]
force_delete = (params[:force_delete] == 'true')
master_record.delete_file(false,current_user.id, force_delete)
upload_records.delete_at(master_record_idx)
if upload_records.count != 0
if master_record.file_manager_trash
trash_path = master_record.file_manager_trash.trash_path
upload_records.each do |record|
record.delete_file(false,current_user.id, force_delete, true, Pathname.new(trash_path).join(record.path.sub(/^#{Regexp.escape(relative_path)}/,'')).to_s)
end
end
end
end
end
end
render :body => nil, :status => 204
end
end
def recover
trash = FileManagerTrash.where(:id=> params[:id]).first
if trash
trash.recover_file
render :body => nil, :status => 204
else
render :body => nil, :status => 404
end
end
def rename
upload = FileManagerUpload.where(:id=>params[:id]).first
absolute_path = upload.path
@root_path = File.dirname(absolute_path)
new_path = safe_expand_path(params[:new_name])
if new_path != absolute_path
if File.exists?(new_path)
render :body => nil, :status => 403
else
relative_path = absolute_path
new_relative_path = Pathname.new(@root_path).join(params[:new_name])
parent = new_path.split('/')[0..-2].join('/')
FileUtils.mkdir_p(parent)
upload_records = get_uploads(relative_path)
FileUtils.mv(absolute_path, new_path)
upload_records.each{|r| r.update_path(new_relative_path)}
render :body => nil, :status => 204
end
else
render :body => nil, :status => 200
end
end
def save
if params[:content].present?
upload_item = FileManagerUpload.where(:id=>params[:id]).first
if upload_item.is_trash
absolute_path = upload_item.file_manager_trash.trash_path
else
absolute_path = upload_item.path
end
forbidden_error and return unless File.exist?(absolute_path)
file_relative_path = absolute_path
is_file = true
new_upload = upload_item.generate_new_upload
File.open(absolute_path,"w+"){|f| f.write(params[:content])}
render :json => {id: new_upload.id.to_s}
else
render :body => nil, :status => 400
end
end
def upload_file
input_file = params[:file]
if input_file
if params[:type] == 'update'
upload_item = FileManagerUpload.where(:id=>params[:id]).first
relative_path = upload_item.path
if input_file.original_filename != upload_item.filename
new_relative_path = Pathname.new(File.dirname(relative_path)).join(input_file.original_filename)
upload_records = get_uploads(relative_path)
FileUtils.mv(relative_path, new_relative_path)
upload_records.each{|r| r.update_path(new_relative_path)}
upload_item.reload
else
new_relative_path = relative_path
end
upload_item = upload_item.generate_new_upload
File.open(new_relative_path, 'wb') do |file|
file.write(input_file.read)
end
else
if params[:id] != 'asset'
forbidden_error and return
end
asset = current_user.assets.new
asset.data = input_file
asset.title_translations = I18n.available_locales.map{|l| [l, input_file.original_filename]}.to_h
asset.save!
upload_item = FileManagerUpload.where(:model=> "Asset", :related_id=> asset.id).first
params[:remote] = true
end
end
if params[:remote].nil?
redirect_to params[:url]
else
render :json => {
id: upload_item.id.to_s,
filename: input_file.original_filename,
size: number_to_human_size(input_file.size),
date: upload_item.created_at.strftime(@format_time)
}
end
end
def settings
FileManagerRoot.create if FileManagerRoot.count == 0
@root = FileManagerRoot.first
end
def update_settings
@root = FileManagerRoot.first
@root.update_attributes(file_manager_root_params)
redirect_to admin_file_managers_settings_path
end
def file_manager_root_params
params.require(:file_manager_root).permit!
end
private
def get_trashes(query_hash={})
query_hash[:file_manager_setting_id] = @setting_id ? @setting_id : FileManagerRoot.first.id
if @module_key && @module_key != 'all'
query_hash[:module] = @module_key
end
@trashes = FileManagerTrash.where(query_hash).order(:created_at=>-1)
end
def get_uploads(tmp_path=nil, is_file=nil, extra_condition={}, order_hash={:version=>-1})
query_hash = {}
if tmp_path != nil
query_hash[:path] = tmp_path
end
if is_file != nil
query_hash[:is_file] = is_file
query_hash[:path] = /^#{Regexp.escape(query_hash[:path].to_s)}/ if query_hash[:path]
end
query_hash[:file_manager_setting_id] = @setting_id if @setting_id
FileManagerUpload.where(query_hash.merge(extra_condition)).order(order_hash).to_a
end
def fix_encoding(str)
if str.valid_encoding?
str
else
try_list = ["utf-8","utf-16", "big-5"]
success_flag = false
try_list.each do |enc|
begin
str.encode!(str.encoding, enc)
if str.valid_encoding?
success_flag = true
break
end
rescue => e
next
end
end
unless success_flag
str = "unknown encoding!"
@unknown_encoding = true
end
str
end
end
def check_editable(path=nil, current_user_id=nil)
query_hash = {:path=>path,:user_id=>current_user_id}
query_hash[:file_manager_setting_id] = @setting_id
FileManagerUpload.where(query_hash).count != 0
end
def my_escape(string)
string.gsub(/([^ a-zA-Z0-9_.-]+)/) do
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
end
end
def populate_directory(module_key, current_url)
if module_key == 'all'
@uploads = FileManagerUpload.where(:is_default=> true)
else
@uploads = FileManagerUpload.where(:module=> module_key, :is_default=> true)
end
@uploads = @uploads.where(:is_trash=> false)
if params[:keywords].present?
@uploads = @uploads.where(:filename=> /#{::Regexp.escape(params[:keywords])}/)
end
@uploads = @uploads.order_by(:created_at => :desc)
@uploads = @uploads.page(params[:page]).per(10)
current_url = '' if current_url == '/'
@module_info_dict = @uploads.map{|upload_item| upload_item.module}.uniq.map{|k| [k, FileManagerSetting.get_module_info(k)]}.to_h
@directory = @uploads.map do |upload_item|
file = upload_item.filename
real_path_absolute = upload_item.path
stat = (File.exist?(real_path_absolute) ? File.stat(real_path_absolute) : nil)
next if stat.nil?
is_file = stat.file?
entry = "#{file}#{is_file ? '': '/'}"
file_relative_path = upload_item.path
is_editable = @default_editable || check_editable(file_relative_path, @current_user_id)
{
module: upload_item.module,
is_file: is_file,
size: ((is_file && stat) ? number_to_human_size(stat.size) : '-'),
type: (is_file ? :file : :directory),
date: (stat ? stat.mtime.strftime(@format_time) : '-'),
relative: my_escape("#{current_url}#{file}").gsub('%2F', '/'),
entry: entry,
absolute: real_path_absolute,
is_editable: is_editable,
id: (upload_item ? upload_item.id.to_s : nil)
}
end.compact
end
def safe_expand_path(path)
current_directory = File.expand_path(@root_path)
tested_path = File.expand_path(path, @root_path)
if @disable_path_traversal && !(tested_path.starts_with?(current_directory))
raise ArgumentError, 'Should not be parent of root'
end
tested_path
end
def check_path_exist(path)
@absolute_path = safe_expand_path(path)
@relative_path = path
raise ActionController::RoutingError, 'Not Found' unless File.exists?(@absolute_path)
@absolute_path
end
def set_base_url
@base_url = ENV['BASE_URL'] || 'root'
@root_path = ENV['BASE_DIRECTORY'] || FileManagerRoot::RootPath
@root = FileManagerRoot.first
@disable_path_traversal = @root.disable_path_traversal
@format_time = I18n.locale.to_s == 'zh_tw' ? '%Y/%m/%d %H:%M' : '%d %b %Y %H:%M'
@setting = FileManagerSetting.first
@only_editable_for_uploader = false
@setting_id = nil
if @setting
@setting_id = @setting.id
@root_path = Pathname.new(@root_path).join(@setting.root_path).to_s
@only_editable_for_uploader = @setting.only_editable_for_uploader
end
@default_editable = !@only_editable_for_uploader
@module_key = params[:id]
@display_module_name = (@module_key == 'all')
@display_uploader_region = (@module_key == 'asset')
@module = ModuleApp.where(:key=> @module_key).first
module_info = FileManagerSetting.get_module_info(@module_key)
@module_title = (module_info ? module_info[:title] : nil)
@only_select_folder = (params[:select_mode] == 'true')
end
end