First version.

This commit is contained in:
邱博亞 2024-05-12 14:22:01 +08:00
commit 6495986188
43 changed files with 3188 additions and 0 deletions

14
Gemfile Normal file
View File

@ -0,0 +1,14 @@
source "https://rubygems.org"
# Declare your gem's dependencies in bulletin.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group.
gemspec
# Declare any dependencies that are still in development here instead of in
# your gemspec. These might include edge Rails or gems from your path or
# Git. Remember to move these dependencies to your gemspec before releasing
# your gem to rubygems.org.
# To use debugger
# gem 'debugger'

6
Rakefile Normal file
View File

@ -0,0 +1,6 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative 'config/application'
Rails.application.load_tasks

0
app/assets/images/.keep Normal file
View File

View File

@ -0,0 +1,13 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require_tree .

View File

@ -0,0 +1,563 @@
var csrf_token = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
$.ajaxSetup({
headers: {
// 'Content-Type': 'multipart/form-data',
// 使用 multipart/form-data 在此不需要設定 Content-Type。
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': csrf_token,
}
});
function replace_explorer_section(file_manager_section, exclude_upload_section){
var org_file_manager_section = $('#file_manager_section');
if(org_file_manager_section.find(' > .file-input').length != 0 && file_manager_section.find('> .file-uploader-script').length != 0){
org_file_manager_section.find(' > *').each(function(i,v){
if(!($(v).hasClass('file-input') || $(v).hasClass('file-uploader-script'))){
$(v).replaceWith(file_manager_section.find('>*').eq(i).clone());
}
})
if(!exclude_upload_section){
$("#file-uploader").fileinput('clear');
var fileinput_data = $("#file-uploader").data('fileinput');
fileinput_data.uploadUrl = "/xhr/file_manager_upload/" + window.current_path + (window.explorer_setting_id ? ('?setting_id='+window.explorer_setting_id) : '');
}
}else{
org_file_manager_section.find('#index_table').html(file_manager_section);
}
}
function reload_explorer_section(url, exclude_upload_section, callback,...args){
if(url == undefined){
url = get_path();
}
$.get(url).done(function(data){
var $file_manager_section = $(data);
replace_explorer_section($file_manager_section, exclude_upload_section);
if(window.scroll_top){
$(window).scrollTop(window.scroll_top);
}
if(typeof(callback) == 'function'){
callback.apply( this, args );
}
}).fail(function() {
alert('No such file or directory!');
});
}
function reload_page(url, is_recycle_bin, extra_params){
var tmp_url = url, request_url;
if(url == undefined){
request_url = get_path(undefined,undefined, extra_params);
}else{
request_url = get_path(url,is_recycle_bin, extra_params);
}
$.get(request_url).done(function(data){
var $file_manager_section = $(data);
replace_explorer_section($file_manager_section);
if(!window.only_select_folder){
push_history_url(request_url);
}
if(window.scroll_top){
$(window).scrollTop(window.scroll_top);
}
}).fail(function() {
alert('No such file or directory!');
});
}
function push_history_url(new_url, state){
if(state == undefined)
state = {body: $('body').prop('outerHTML')};
if(history.state == null){
history.replaceState({need_reload: true} ,$('title').text(), window.location.href);
}
window.history.pushState(state ,$('title').text(), new_url);
}
function get_current_url(path, override_recycle_bin, extra_params){
if(override_recycle_bin == undefined){
override_recycle_bin = window.is_recycle_bin;
}
var params = {};
if(override_recycle_bin){
path = '';
params['recycle_bin'] = true
}
if(extra_params){
Object.keys(extra_params).forEach(function(k){
params[k] = extra_params[k];
})
}
var url = '';
if(window.path_mode == 'params'){
params["path"] = path;
url = window.location.pathname
}else{
url = "/xhr/file_manager/" + path;
}
if(window.only_select_folder){
params['select_mode'] = true;
}
return url + params_to_query(params);
}
function get_path(path, override_recycle_bin, extra_params){
var params = get_params();
if(path == undefined){
path = window.location.pathname;
}
if(override_recycle_bin == undefined){
override_recycle_bin = window.is_recycle_bin;
}
if(override_recycle_bin){
path = ""
params["recycle_bin"] = true;
window.is_recycle_bin = true;
}else{
delete params["recycle_bin"];
window.is_recycle_bin = false;
}
if(window.only_select_folder){
params['select_mode'] = true;
}else{
delete params["select_mode"];
}
delete params["page"];
delete params["file_id"];
if(window.explorer_setting_id){
params['setting_id'] = window.explorer_setting_id;
}
if(extra_params){
Object.keys(extra_params).forEach(function(k){
if (extra_params[k] != null)
params[k] = extra_params[k];
})
}
if(window.use_params_request){
params['path'] = path;
path = '';
}else{
delete params['path'];
}
return path + params_to_query(params);
}
function get_basename(file){
var tmp = file.split('/');
return tmp[tmp.length - 1] || '';
}
function pathJoin(parts, sep){
var separator = sep || '/';
var replace = new RegExp(separator+'{1,}', 'g');
if(parts.length > 1){
parts.forEach(function(p, i){
if(p[0] == '/'){
for(var k=0; k<i; k++){
parts[k] = "";
}
}
})
}
parts = parts.filter(function(s){ return s != "" });
var new_path = parts.join(separator).replace(replace, separator).replace(new RegExp(`[^${separator}]+${separator}\\.\\.${separator}`, 'g'),'');
return new_path.replace(/^(http|ftp):\//,'$1://').replace(/file:\//,'file:///').replace(new RegExp(`^.{1,2}${separator}`),'').replace(/^\/\.\.\//,'/').replace(/\.\//g,'');
}
function get_current_path(path, override_recycle_bin){
if(path == undefined){
path = window.current_path;
}
if(override_recycle_bin == undefined){
override_recycle_bin = window.is_recycle_bin;
}
if(override_recycle_bin && window.recycle_bin_text){
return window.recycle_bin_text;
}else{
return path;
}
}
function change_folder(id, folder, is_recycle_bin){
window.scroll_top = $('#file_manager_section thead').scrollTop();
if (!folder) {
folder = window.module_key;
}
var new_path = window.file_manager_path + '/' + folder + '/module';
new_path = get_path(new_path, (is_recycle_bin ? true : false), {"file_id": id});
console.log(new_path, id);
$.get(new_path).done(function(data){
var $file_manager_section = $(data);
replace_explorer_section($file_manager_section);
window.module_key = folder;
if(!window.only_select_folder){
push_history_url(new_path);
}
if(window.scroll_top){
$(window).scrollTop(window.scroll_top);
}
}).fail(function() {
alert('No such file or directory!');
});
}
function get_params(){
var search_split = window.location.search.split("?");
var params = {}
if( search_split.length >= 2 ){
$(search_split[1].split("&")).each(function(i, s){
var tmp = s.split("=");
var k = tmp[0], v = tmp[1];
if(k.search('[]') != -1){
k = k.split('[]')[0];
if(params.hasOwnProperty(k)){
params[k].push(v);
}else{
params[k] = [v];
}
}else{
params[k] = v;
}
})
}
return params;
}
function params_to_query(params){
var query_text = '';
var keys = Object.keys(params);
if(keys.length != 0){
keys.forEach(function(k, i){
if(i == 0){
query_text += '?';
}else{
query_text += '&';
}
var v = params[k];
if(Array.isArray(v)){
v.forEach(function(vv, j){
if(j != 0){
query_text += '&';
}
query_text += `${k}[]=${vv}`;
})
}else{
query_text += `${k}=${v}`;
}
})
}
return query_text;
}
$(document).on("click",".delete",function(){
var that = $(this);
window.scroll_top = $(window).scrollTop();
if( window.confirm(that.data("tmp-confirm")) ){
if( window.confirm(that.data("tmp-confirm")) ){
$.ajax({
url: that.data("link"),
method: 'DELETE',
contentType: false, //required
processData: false, // required
statusCode: {
204: reload_explorer_section,
404: function() {
alert( "Delete failed!\nFile not exist!" );
}
}
})
}
}
})
$(document).on("click",".delete_file",function(){
var that = $(this);
window.scroll_top = $(window).scrollTop();
if( window.confirm(that.data("tmp-confirm")) ){
if( window.confirm(that.data("tmp-confirm")) ){
$.ajax({
url: that.data("link"),
method: 'DELETE',
contentType: false, //required
processData: false, // required
dataType: 'json',
statusCode: {
200: function(data) {
var params = get_params();
var extra_params;
if (data.id) {
extra_params = {"version": data.version, "file_id": data.id};
} else {
extra_params = {};
}
var url = get_path(undefined, undefined, extra_params);
var callback = push_history_url;
reload_explorer_section(url, false, callback, url);
},
404: function() {
alert( "Delete failed!\nFile not exist!" );
}
}
})
}
}
})
$(document).on("click",".edit",function(){
$(this).hide();
var pre_height = $('#file_manager_section pre').height();
$('#file_manager_section textarea.edit-area').html($('#file_manager_section pre').html());
$('#file_manager_section textarea.edit-area').removeClass('hide');
$('#file_manager_section pre').addClass('hide');
$('#file_manager_section textarea.edit-area').height(pre_height);
$(".save").removeClass('hide');
})
$(document).on("click",".save",function(){
$(this).hide();
var data = new FormData();
data.append("content", $('#file_manager_section textarea.edit-area').val());
$.ajax({
url: '/admin/file_managers/' + $(this).data('id') + '/save',
method: 'POST',
data: data,
contentType: false, //required
processData: false, // required
dataType: 'json',
mimeType: 'multipart/form-data',
statusCode: {
200: function(data) {
console.log(data);
var params = get_params(), url;
var callback = push_history_url;
if(params.version){
var new_version = Number(params.version) + 1;
url = get_path(undefined, undefined, {"version": new_version, "file_id": data.id});
params.version = new_version.toString();
} else {
url = get_path(undefined, undefined, {"file_id": data.id});
}
reload_explorer_section(url, false, callback, url);
$(".save").addClass('hide');
$(".edit").removeClass('hide');
},
400: function() {
alert( "Bad Request!\nThere are no content of this file!" );
}
}
})
})
$(document).on("click",".module-key",function(){
change_folder(null, $(this).data("key"));
})
$(document).on("click",".directory, .file",function(){
change_folder($(this).data("id"));
})
$(document).on("click",".breadcrumb-index",function(){
var path = window.current_path[0] == '/' ? window.current_path : ('/' + window.current_path);
$("#breadcrumb-relative-url").addClass('hide');
$("#breadcrumb-editable").text(path).removeClass("hide").focus();
window.tmp_current_path = path;
})
$(document).on("click",'#breadcrumb-relative-url .directory, #breadcrumb-editable',function(e){
e.stopPropagation();
})
$(document).on("blur","#breadcrumb-editable",function(){
if(window.tmp_current_path != $(this).text().trim()){
change_folder($(this).text().trim());
}
$("#breadcrumb-relative-url").removeClass('hide');
$(this).addClass('hide');
})
$(document).on("keydown","#breadcrumb-editable",function(evt){
if(evt.keyCode == 13){
evt.preventDefault();
$(this).trigger("blur");
}
})
$(document).on("click", ".copyButton", function(){
var link = $(this).data('link');
// Copy the link
navigator.clipboard.writeText(link);
if (!($(this).data('org-text')))
$(this).data('org-text', $(this).find('.tooltiptext').text());
$(this).find('.tooltiptext').text($(this).data('prompt') + ': ' + link);
})
$(document).on("mouseout", ".copyButton", function(){
var org_text = $(this).data('org-text');
if (org_text)
$(this).find('.tooltiptext').text(org_text);
})
$(document).on("click", ".redirect_recycle_bin", function(){
change_folder(null, null, true);
})
$(document).on("click", ".recycle_bin", function(){
change_folder(null, $(this).data('key'), true);
})
$(document).on("click", ".recover_all", function(){
var that = $(this);
var prompt_message = that.data("prompt");
$.ajax({
url: '/admin/file_managers/' + window.module_key + '/get_trash_count',
method: 'POST',
contentType: false, //required
processData: false, // required
dataType: 'json',
mimeType: 'multipart/form-data',
statusCode: {
200: function(data) {
var count = data["count"] + that.data('unit');
prompt_message = prompt_message.replace('${num}', count);
if(window.confirm(prompt_message)){
if(window.confirm(prompt_message)){
$.ajax({
url: '/admin/file_managers/' + window.module_key + '/recover_all',
method: 'POST',
contentType: false, //required
processData: false, // required
dataType: 'json',
mimeType: 'multipart/form-data',
statusCode: {
200: function(data) {
var thread_id = data["id"];
var thread_title = data["title"];
reload_explorer_section(get_path(), false, function show_modal(thread_id, thread_title){
$("#threadModal").data('id', thread_id);
$("#threadModal .modal-header h3").text(thread_title);
$("#threadModal").removeClass('hide').modal('show');
}, thread_id, thread_title);
}
}
})
}
}
}
}
})
})
$(document).on("click",".clean_up_recycle_bin", function(){
var that = $(this);
var prompt_message = that.data("prompt");
$.ajax({
url: '/admin/file_managers/' + window.module_key + '/get_trash_count',
method: 'POST',
contentType: false, //required
processData: false, // required
dataType: 'json',
mimeType: 'multipart/form-data',
statusCode: {
200: function(data) {
var count = data["count"] + that.data('unit');
prompt_message = prompt_message.replace('${num}', count);
if(window.confirm(prompt_message)){
if(window.confirm(prompt_message)){
$.ajax({
url: '/admin/file_managers/' + window.module_key + '/clean_trashes',
method: 'POST',
contentType: false, //required
processData: false, // required
dataType: 'json',
mimeType: 'multipart/form-data',
statusCode: {
200: function(data) {
var thread_id = data["id"];
var thread_title = data["title"];
reload_explorer_section(get_path(), false, function show_modal(thread_id, thread_title){
$("#threadModal").data('id', thread_id);
$("#threadModal .modal-header h3").text(thread_title);
$("#threadModal").removeClass('hide').modal('show');
}, thread_id, thread_title);
}
}
})
}
}
}
}
})
})
$(document).on('hidden.bs.modal', "#threadModal", function(){
window.clearTimeout(window.time_out_id);
})
$(document).on('shown.bs.modal', "#threadModal", function(){
var thread_id = $(this).data('id');
window.time_out_id = window.setTimeout(function(){
update_thread(thread_id, reload_page);
}, 1000);
})
$(document).on('click', ".change_version", function(){
var that = $(this);
var version = that.data('version');
var file_id = that.data('id');
reload_page(undefined,undefined,{"version": version, "file_id": file_id});
})
$(document).on('click', ".recover", function(){
var that = $(this);
window.scroll_top = $(window).scrollTop();
if( window.confirm(that.data("tmp-confirm")) ){
$.ajax({
url: that.data("link"),
method: 'POST',
contentType: false, //required
processData: false, // required
statusCode: {
204: reload_explorer_section,
404: function() {
alert( "Recover failed!\nFile not exist!" );
}
}
})
}
})
function update_thread(id, finish_callback, ...args){
var data = new FormData();
data.append('id',id);
$.ajax({
url: "/admin/threads/get_status",
method: 'POST',
data: data,
contentType: false, //required
processData: false, // required
dataType: 'json',
mimeType: 'multipart/form-data',
statusCode: {
200: function(data){
var finish_percent = data["finish_percent"];
var current_count = data["current_count"];
var all_count = data["all_count"];
var is_finish = (data["status"] == "finish");
if(finish_percent){
$("#threadModal .modal-body .thread-status").text(data["status"]);
if(data["status"] != 'Processing'){
$("#threadModal .modal-body .thread-current-count").text(current_count);
$("#threadModal .modal-body .thread-all-count").text(all_count);
}
$("#threadModal .modal-body .thread-finish_percent").text(finish_percent);
}else if(is_finish){
$("#threadModal .modal-body .thread-status").text(data["status"]);
$("#threadModal .modal-body .thread-finish_percent").text(100);
}
var that = this;
if(!is_finish){
var tmp_arguments = arguments;
window.time_out_id = window.setTimeout(function(){
update_thread.apply(that, tmp_arguments);
}, 1000);
}else{
if(window.time_out_id)
window.clearTimeout(window.time_out_id);
var wait_time = 3000;
if(finish_callback)
wait_time = 0;
window.setTimeout(function(){
$("#threadModal").modal("hide");
if(finish_callback){
finish_callback.apply(that, args);
}
alert(data["status"]);
}, wait_time);
return;
}
}
}
});
}
window.onpopstate = function(event) {
var state = event.state;
if(state != null){
if(state.need_reload){
window.location.reload();
}else if(state.body){
var $body = $(state.body);
$('.breadcrumb-index').replaceWith($body.find('.breadcrumb-index'));
$('#file_manager_section').replaceWith($body.find('#file_manager_section'));
}
}
};

View File

@ -0,0 +1,41 @@
function rename(id, name) {
window.scroll_top = $(window).scrollTop();
var path = pathJoin([name,".."]);
var basename = get_basename(name);
var new_name = prompt("Enter the new name for this element.", basename);
if (new_name != null) {
new_name = pathJoin([path, new_name]);
var data = new FormData();
data.append("new_name", new_name);
var csrf_token = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
var csrf_param = document
.querySelector("meta[name='csrf-param']")
.getAttribute("content");
if (csrf_token && csrf_param) {
data.append(csrf_param, csrf_token);
}
$.ajax({
url: '/admin/file_managers/' + id + '/rename',
method: 'PUT',
data: data,
headers: {
// 'Content-Type': 'multipart/form-data',
// 使用 multipart/form-data 在此不需要設定 Content-Type。
'X-Requested-With': 'XMLHttpRequest',
'Authorization': `Bearer ${ csrf_token }`,
},
contentType: false, //required
processData: false, // required
mimeType: 'multipart/form-data',
statusCode: {
204: reload_explorer_section,
403: function() {
alert( "Rename failed!\nFile exist!" );
}
}
})
}
return false;
}

View File

@ -0,0 +1,3 @@
function abc(){
console.log(12341111222444551111111111111111111111111111111111111111)
}

View File

@ -0,0 +1,270 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_self
*= require fontawesome-5.15.4.min
*/
/* Common */
input[disabled], select[disabled], textarea[disabled], input[readonly], select[readonly], textarea[readonly] {
cursor: not-allowed;
background-color: #eeeeee;
}
body#file_manager_body, html{
font-size: 1rem !important;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
#file_manager_section h3 {
float: left;
}
#file_manager_section .btn-wrapper {
float: right;
}
#file_manager_section .btn-wrapper .upload-button {
display: inline-block;
}
#file_manager_section table {
border-collapse: collapse;
width: 100%;
text-align: left;
table-layout: fixed;
}
.modal table {
white-space: unset;
}
.modal.fade:not(.in) {
display: none;
}
.modal-backdrop.fade:not(.in) {
display: none;
}
#file_manager_section th {
font-size: 1.17em;
background-color: rgb(76, 102, 175);
color: white;
}
#file_manager_section tr:hover {
background-color: #ffffff;
}
#file_manager_section th, #file_manager_section td {
padding: 4px 12px;
border: 1px solid;
border-color: #ddd;
}
#file_manager_section td a {
display: block;
width: 100%;
overflow-wrap: break-word;
}
/* Light mode: */
a.delete,
a.delete:visited {
color: rgb(223, 132, 136);
}
a.rename, a.recover {
color: deepskyblue;
}
.date {
font-family: 'Courier New', Courier, monospace;
}
.name {
width: 100%;
white-space: normal;
}
.fa {
padding: 0 6px 0 0;
}
.download, .create_folder, .edit, .save, .recover_all, .clean_up_recycle_bin, .delete_file,
.download:link, .create_folder:link, .edit:link, .save:link, .recover_all:link, .clean_up_recycle_bin:link, .delete_file:link,
.download:visited, .create_folder:visited, .edit:visited, .save:visited, .clean_up_recycle_bin:visited, .delete_file:visited {
color: white;
display: inline-block;
font-size: 1.17em;
padding: 12px 18px;
text-align: center;
text-decoration: none;
white-space: pre-wrap;
border: none;
cursor: pointer;
box-sizing: border-box;
line-height: 1.0;
}
input[type=file]#file{
display: inline-block;
}
form {
display: inline-block;
}
#file_manager_section pre,#file_manager_section textarea {
padding: 10px;
white-space: pre-wrap;
word-wrap: break-word;
}
.save.hide{
display: none;
}
.breadcrumb-index {
display: inline-flex;
margin: 0.67em 0;
font-size: 2em;
}
.breadcrumb-index, #breadcrumb-editable {
width: 100%;
}
breadcrumb-relative-url{
max-width: 100%;
overflow-wrap: break-word;
}
.breadcrumb-index a {
color: #0366d6;
font-weight: normal;
}
.breadcrumb-index a.base_url {
font-weight: bold;
}
.separator {
padding: 0 10px;
}
.create_folder {
float: right;
background: forestgreen;
}
.recover_all {
background: deepskyblue;
}
.create_folder, .clean_up_recycle_bin {
float: right;
background: red;
}
.fa-folder {
color: rgba(3,47,98,.5);
}
.fa-file {
color: rgb(155, 155, 155);
}
.download {
background-color: rgb(60, 75, 209);
}
.delete_file {
background: #be0606;
color: white;
}
.edit {
background: #a67030;
}
.save{
background: #00e300;
color: black;
}
#file_manager_section pre,#file_manager_section textarea {
background-color: #d3d3d3;
color: black;
}
#file_manager_section textarea{
width: calc(100% - 20px);
}
.redirect_recycle_bin{
float: right;
}
.dropdown-menu.upload-box {
right: 0;
left: auto;
}
.tooltip {
position: relative;
display: inline-block;
opacity: 1;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 140px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 150%;
left: 50%;
margin-left: -75px;
opacity: 0;
transition: opacity 0.3s;
overflow-wrap: break-word;
}
.tooltip .tooltiptext::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #555 transparent transparent transparent;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
@media (prefers-color-scheme: dark) {
body#file_manager_body, #file_manager_section tbody {
background-color: black;
color: rgba(249, 249, 250, 0.8);
}
.fa-folder {
color: rgb(2, 69, 146);
}
.fa-file {
color: rgb(177, 177, 177);
}
a .filename {
color: rgb(47, 121, 165);
}
a.delete, a.create_folder, a.clean_up_recycle_bin, a.edit, a.delete_file,
a.delete:link, a.create_folder:link, a.clean_up_recycle_bin:link, a.edit:link, a.delete_file:link,
a.delete:visited, a.create_folder:visited, a.clean_up_recycle_bin:visited, a.edit:visited, a.delete_file:visited {
color: rgb(223, 132, 136);
}
tr:hover {
background-color: #3b3b3b;
}
th, td {
border-color: rgb(138, 138, 138);
}
.download {
background-color: rgb(29, 42, 163);
}
.create_folder {
background: darkgreen;
}
.recover_all {
background: darkred;
}
.edit {
background: #653700;
}
.save {
background: green;
color: #fff;
}
#file_manager_section pre,#file_manager_section textarea {
background-color: #3b3b3b;
color: white;
}
}

View File

@ -0,0 +1,35 @@
li {
list-style: none;
}
ul.tab_nav {
list-style-type: none;
padding: 0;
display: flex;
flex-wrap: wrap;
font-family: "Roboto", "微軟正黑體", "Helvetica Neue", Helvetica, sans-serif;
}
ul.tab_nav li {
padding: 0.5em 1em;
background: #32D9C3;
margin: 0.2em;
cursor: pointer;
transition: all 0.5s;
-moz-transition: all 0.5s;
-webkit-transition: all 0.5s;
-o-transition: all 0.5s;
}
ul.tab_nav li.active {
background: #19524b;
color: #fff;
}
@media (prefers-color-scheme: dark) {
ul.tab_nav li {
background-color: #f3f3f3;
color: #ccc;
}
ul.tab_nav li.active {
background-color: #969696;
color: #fff;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,499 @@
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

View File

@ -0,0 +1,97 @@
class FileManagersController < ApplicationController
include ActionView::Helpers::NumberHelper
before_action :check_login? , :set_base_url, except: [:download]
layout "file_manager"
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 self.custom_widget_data
@root = FileManagerRoot.first
@settings = @root.file_manager_settings rescue []
ac = ActionController::Base.new
ac.render_to_string("file_managers/custom_widget_data",:locals=>{:@custom_data_field=>@custom_data_field,:@field_name=>@field_name,:@settings=>@settings})
end
def check_login?
@current_user = current_user
@current_user_id = current_user.id
if @current_user
if ['index_backend','path'].include?( params[:action] ) && params[:setting_id].blank?
module_app = ModuleApp.where(:key=>'file_manager').first
unless (@current_user.is_admin_for_module?(module_app) rescue true)
render_403 and return
end
end
else
render_403 and return
end
end
def download
upload = FileManagerUpload.where(:id=>params[:id]).first
if upload
if upload.is_trash
if upload.file_manager_trash
send_file(upload.file_manager_trash.trash_path)
else
render_404
end
else
options = {}
if params[:preview]
options[:disposition] = 'inline'
end
send_file(upload.get_real_path, options)
end
else
render_404
end
end
private
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 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(tmp_params=params)
@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'
if tmp_params[:setting_id].present?
@setting = FileManagerSetting.find(tmp_params[:setting_id]) rescue nil
end
@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
@only_select_folder = (tmp_params[:select_mode] == 'true')
end
end

View File

@ -0,0 +1,54 @@
module FileManagersHelper
def fa_icon(icon_name, text="")
"<i class=\"fa fa-#{icon_name}\">#{text}</i>".html_safe
end
def generate_breadcrumb
breadcrumb_contents = ""
url_infos = [
{
type: "key",
key: "all",
trans: I18n.t(:all)
}
]
if @module_key != 'all'
url_infos << {
type: "key",
key: @module_key,
trans: @module_title
}
end
if @recycle_bin || (@upload_item && @upload_item.is_trash)
url_infos << {
type: "recycle",
key: (@upload_item ? @upload_item.module : nil),
trans: I18n.t("file_manager.recycle_bin")
}
end
if @file
filename = @upload_item.filename
version = @upload_item.version
if @upload_item.is_trash && @upload_item.file_manager_trash.record_only
filename += " (version #{version})"
end
url_infos << {
type: "file",
trans: filename
}
end
last_idx = url_infos.count - 1
url_infos.each_with_index do |info, i|
if i == last_idx
breadcrumb_contents += info[:trans]
else
if info[:type] == 'recycle'
breadcrumb_contents += "<a href=\"javascript:void(0)\" class=\"recycle_bin\" data-key=\"#{info[:key]}\">#{info[:trans]}</a>"
else
breadcrumb_contents += "<a href=\"javascript:void(0)\" data-key=\"#{info[:key]}\" class=\"module-key\">#{info[:trans]}</a>"
end
breadcrumb_contents += "<span class=\"separator\">/</span>"
end
end
breadcrumb_contents.html_safe
end
end

View File

@ -0,0 +1,8 @@
class FileManagerRoot
include Mongoid::Document
include Mongoid::Timestamps
RootPath = "public/"
field :disable_path_traversal, :type => Boolean, :default => true
has_many :file_manager_settings, :autosave => true, :dependent => :destroy
accepts_nested_attributes_for :file_manager_settings, :allow_destroy => true
end

View File

@ -0,0 +1,284 @@
class FileManagerSetting
include Mongoid::Document
include Mongoid::Timestamps
field :only_editable_for_uploader, type: Boolean, default: false
field :root_path, type: String, default: ""
field :modules_saved_info, type: Hash, default: {}
field :modules_keys, type: Array, default: []
belongs_to :file_manager_root
has_many :file_manager_uploads
has_many :file_manager_trashes
@@mutex = Mutex.new
@@resource = ConditionVariable.new
def self.get_models_uploaders
if !self.class_variable_defined?(:@@get_models_uploaders)
@@get_models_uploaders = false
end
if @@get_models_uploaders
@@mutex.synchronize {
@@resource.wait(@@mutex)
}
end
@@get_models_uploaders = true
if self.class_variable_defined?(:@@modules_info)
@@get_models_uploaders = false
return @@modules_info
end
setting = self.first
if setting.nil?
setting = self.create
end
modules_info = {}
file_only_models = []
user_id_fields = ['user_id', 'create_user_id', 'update_user_id']
user_id_field_info = {}
Rails::Engine.descendants.each do |engine|
unless engine.abstract_railtie? || engine.initializers.blank?
module_name = engine.initializers[0].name
if ModuleApp.where(:key=> module_name).count > 0
models_path = Dir.glob("#{engine.root}/app/models/*")
uploaders = {}
models_path.each do |path|
begin
require File.basename(path)
m = File.basename(path).sub(/\..+/, '').camelize.constantize
if m.uploaders.count > 0
user_id_field = nil
user_id_fields.each do |f|
if m.fields.include?(f)
user_id_field = f
break
end
end
if user_id_field.nil?
user_id_field = m.fields.select{|f| f.include?('user_id')}.first
end
user_id_field_info[m] = user_id_field
uploaders_info = {}
obj = m.new(:id=>nil)
m.uploaders.each do |field, uploader_klass|
uploaders_info[field] = {
uploader_klass => "public/" + obj.send(field).store_dir
}
end
self.set_record_callback(setting, module_name, m, user_id_field)
uploaders[m] = uploaders_info
m_str = m.to_s
if m_str.match(/(File|Image)$/)
file_only_models << m_str
else
belongs_to_fields = m.relations.select{|k,v| v.macro == :belongs_to}.keys
if belongs_to_fields.count > 0 && (m.relations.count - belongs_to_fields.count) == 0
fields = m.fields.keys - belongs_to_fields.map{|s| "#{s}_id"}
fields = fields - ["_id", "created_at", "updated_at", "description", "title", "title_translations", "rss2_id", "order", "choose_lang", "privacy_type", "should_destroy", "file_title", "download_count", "sort_number"]
if (fields - m.uploaders.keys.map(&:to_s)).count == 0
file_only_models << m_str
end
end
end
end
rescue LoadError
end
end
if uploaders.count > 0
modules_info[module_name] = uploaders
end
end
end
end
begin
require 'asset'
module_name = 'asset'
m = Asset
uploaders = {}
if m.uploaders.count > 0
uploaders_info = {}
obj = m.new(:id=>nil)
m.uploaders.each do |field, uploader_klass|
uploaders_info[field] = {
uploader_klass => "public/" + obj.send(field).store_dir
}
end
uploaders[m] = uploaders_info
user_id_field = nil
user_id_fields.each do |f|
if m.fields.include?(f)
user_id_field = f
break
end
end
if user_id_field.nil?
user_id_field = m.fields.select{|f| f.include?('user_id')}.first
end
user_id_field_info[m] = user_id_field
self.set_record_callback(setting, module_name, m, user_id_field)
user_id_field
end
modules_info[module_name] = uploaders
file_only_models << m.to_s
rescue LoadError
end
@@file_only_models = file_only_models
@@user_id_field_info = user_id_field_info
@@modules_info = modules_info
@@mutex.synchronize {
@@get_models_uploaders = false
@@resource.signal
}
modules_info
end
def self.set_record_callback(setting, module_name, m, user_id_field)
uploader_fields = m.uploaders.keys
m_str = m.to_s
m.before_save do |record|
uploader_fields.each{|f| record.instance_variable_set("@#{f}_changed", record.send("#{f}_changed?"))}
true
end
m.after_save do |record|
uploader_fields.each do |f|
f_changed = "@#{f}_changed"
if record.instance_variable_get(f_changed)
store_path = "public#{URI.decode(record.send(f).url)}"
if user_id_field
user_id = record.send(user_id_field)
else
user_id = nil
end
basic_info = {:module=> module_name, :model=>m_str, :related_field=> f, :related_id => record.id}
other_info = {:path=> store_path, :user_id=> user_id, :is_default=>true, :created_at => record.created_at, :updated_at => record.updated_at}
full_info = other_info.merge(basic_info)
old_record = setting.file_manager_uploads.where(basic_info).first
if old_record.nil?
setting.file_manager_uploads.create(full_info)
else
old_record.update_attributes(other_info)
end
end
record.remove_instance_variable(f_changed) unless record.instance_variable_get(f_changed).nil?
end
true
end
m.after_destroy do |record|
unless record.instance_variable_get(:@skip_remove_callback)
setting.file_manager_uploads.where(:model=>m_str, :related_id => record.id).destroy
end
true
end
end
def self.file_only_models
unless self.class_variable_defined?(:@@file_only_models)
self.get_models_uploaders
end
return @@file_only_models
end
def self.get_module_info(key)
if key == 'all'
title = I18n.t("file_manager.all_files")
link = nil
icon_class = 'icons-disk'
sidebar = nil
elsif key == 'asset'
title = I18n.t('filemanager')
link = Rails.application.routes.url_helpers.admin_assets_path
icon_class = 'icons-folder'
sidebar = nil
else
module_app = ModuleApp.where(:key=>key).first
return if module_app.nil?
t = module_app.get_registration
return if t.nil?
sidebar = t.get_side_bar
if sidebar
t = sidebar
icon_class = t.get_icon_class
module_label = t.instance_variable_get(:@head_label)
link = t.instance_variable_get(:@head_link)
if link
link = Rails.application.routes.url_helpers.send(link)
end
else
icon_class = module_app.icon_class_no_sidebar
module_label = t.instance_variable_get(:@module_label)
plugin_t = OrbitApp::Plugin::Registration.find_by_module_app_name(module_app.title)
link = plugin_t.admin_partial_path if plugin_t
end
title = I18n.t(module_label)
end
{
:title => title,
:link => link,
:icon_class => icon_class,
:sidebar => sidebar
}
end
def create_records
modules_info = self.class.get_models_uploaders
models_dict = self.modules_saved_info
modules_info.each do |module_key, uploaders|
uploaders.each do |m, uploaders_info|
m_str = m.to_s
if models_dict[m_str].nil?
models_dict[m_str] = nil
end
end
end
self.modules_saved_info = models_dict
self.modules_keys = modules_info.keys
self.save
modules_info.each do |module_key, uploaders|
# module_app = ModuleApp.where(:key=> module_key).first
# I18n.t("module_name.#{module_key}")
uploaders.each do |m, uploaders_info|
user_id_field = @@user_id_field_info[m]
uploader_fields = uploaders_info.keys
records = m.any_of(uploader_fields.map{|f| {f => {"$ne" => nil}}})
m_str = m.to_s
if self.modules_saved_info[m_str]
records = records.where(:created_at.gte=> (self.modules_saved_info[m_str] - 1))
end
records = records.order_by(:created_at=> :asc).to_a
records_last_idx = records.length - 1
records.each_with_index do |r, i|
uploader_fields.each do |f|
f_value = r.send(f)
if f_value.present?
store_path = "public#{URI.decode(f_value.url)}"
unless File.exist?(store_path)
next
end
if user_id_field
user_id = r.send(user_id_field)
else
user_id = nil
end
basic_info = {:module=> module_key, :model=> m.to_s, :related_field=> f, :related_id => r.id, :is_default=> true}
other_info = {:path=> store_path, :user_id=> user_id, :created_at => r.created_at, :updated_at => r.updated_at}
full_info = other_info.merge(basic_info)
old_record = self.file_manager_uploads.where(basic_info).first
if old_record.nil?
self.file_manager_uploads.create(full_info)
else
old_record.update_attributes(other_info)
end
end
end
if i % 10 || i == records_last_idx
puts "#{m} updated at #{r.created_at}"
self.class.where(:id=>self.id).update_all("modules_saved_info.#{m}" => r.created_at.to_i)
end
end
end
end
end
def get_modules_keys
_modules_keys = self.modules_keys.sort
_modules_keys = ["all"] + ["asset"] + (_modules_keys - ["asset"])
end
end

View File

@ -0,0 +1,83 @@
class FileManagerTrash
include Mongoid::Document
include Mongoid::Timestamps
Restrict_dirs = ["/", "/home/", "/etc/", "/var/*/", "/root/", "/usr/*/"]
field :is_file, type: Boolean, default: true
field :module, type: String, default: ""
field :path, type: String, default: ""
field :trash_path, type: String, default: ""
field :user_id
field :is_slave, type: Boolean, default: false
field :record_only, type: Boolean, default: false
belongs_to :file_manager_upload, index: true
belongs_to :file_manager_setting
index({module: 1}, { unique: false, background: true })
index({created_at: -1}, { unique: false, background: true })
before_create do
if self.trash_path.blank?
self.trash_path = Pathname.new("#{FileManagerRoot::RootPath}").join(self.file_manager_setting && self.file_manager_setting.class == FileManagerSetting ? self.file_manager_setting.root_path : '').join(".trash/#{self.file_manager_setting_id}/#{self.id}/#{File.basename(self.path)}")
trash_dir = File.dirname(self.trash_path)
FileUtils.mkdir_p(trash_dir)
upload = self.file_manager_upload
if File.exist?(upload.get_real_path)
FileUtils.mv(upload.get_real_path, self.trash_path)
end
end
end
before_destroy do
upload = self.file_manager_upload
if upload
if upload.is_default
upload.remove_file_callback
end
if self.record_only
upload.destroy
else
FileManagerUpload.where(:path=> upload.path).destroy
end
end
true
end
def get_version
return (self.file_manager_upload ? self.file_manager_upload.version : 1)
end
def delete_file
self.class.delete_file_permanent(self.path, File.dirname(self.trash_path), self.record_only)
unless self.is_file
self.class.where(:trash_path=>/^#{::Regexp.escape(self.trash_path)}/,:is_slave=>true).destroy
end
self.destroy
end
def self.check_restricted(path)
return Restrict_dirs.map{|d| path.match("^#{d.gsub('/*/','/.*')}[^/]+(/|)$")}.compact.length > 0
end
def self.delete_file_permanent(path, trash_path=nil, only_self=false)
unless self.check_restricted(path)
dir = File.dirname(path)
basename = File.basename(path)
if trash_path
FileUtils.rm_rf(trash_path)
else
FileUtils.rm_rf(path)
end
unless only_self
Dir.glob("#{dir}/.versions/*/#{basename}").each{|f| FileUtils.rm_rf(f)}
end
else
puts "Path: #{path}"
puts "File or Directory restricted!"
end
end
def recover_file
if File.exist?(self.trash_path)
dir = File.dirname(self.path)
FileUtils.mkdir_p(dir)
FileUtils.mv(self.trash_path, self.path)
end
self.file_manager_upload.update(:is_trash=>false) if self.file_manager_upload
unless self.is_file
self.class.where(:trash_path=>/^#{::Regexp.escape(self.trash_path)}/,:is_slave=>true).delete
end
self.delete
end
end

View File

@ -0,0 +1,172 @@
class FileManagerUpload
include Mongoid::Document
include Mongoid::Timestamps
Restrict_dirs = ["/", "/home/", "/etc/", "/var/*/", "/root/", "/usr/*/"]
field :module, type: String, default: ""
field :model, type: String, default: ""
field :related_field, type: String, default: ""
field :related_id, type: BSON::ObjectId
field :is_file, type: Boolean, default: true
field :filename, type: String, default: ""
field :path, type: String, default: ""
field :version, type: Integer, default: 1
field :is_default, type: Boolean, default: false
field :user_id
field :is_trash, type: Boolean, default: false
belongs_to :file_manager_setting
has_one :file_manager_trash
index({model: 1, related_field: 1, related_id: 1}, { unique: false, background: true })
index({module: 1}, { unique: false, background: true })
index({version: -1}, { unique: false, background: true })
index({created_at: -1}, { unique: false, background: true })
index({path: 1}, { unique: false, background: true })
index({filename: 1}, { unique: false, background: true })
index({is_trash: 1}, { unique: false, background: true })
before_save do
self.filename = File.basename(self.path)
if self.filename_changed? && self.related_id.present?
m = self.model.constantize
info = {self.related_field => self.filename}
if self.model == 'Asset'
I18n.available_locales.each do |l|
info["title.#{l}"] = self.filename
end
end
m.where(:id=> self.related_id).update_all(info)
end
true
end
def self.check_restricted(path)
return Restrict_dirs.map{|d| path.match("^#{d.gsub('/*/','/.*')}[^/]+(/|)$")}.compact.length > 0
end
def get_related
begin
m = self.model.constantize
related = m.where(:id=> self.related_id).first
rescue => e
related = nil
puts "get_related: #{e}"
end
related
end
def generate_new_upload
clone_record = self.clone
clone_record.is_trash = false
clone_record.created_at = Time.now
clone_record.updated_at = Time.now
self.update_path(self.path, self.is_default ,true)
clone_record.version += 1
clone_record.save
clone_record
end
def delete_file(record_only=false,override_user_id=nil, real_delete=false, is_slave=false, trash_path=nil)
if !real_delete
unless self.is_trash
unless self.is_file
if !is_slave && Dir.glob(Pathname.new(self.path).join("*").to_s).count == 0 #remove empty dir
self.delete_file_permanent
return
end
end
override_user_id = self.user_id if override_user_id.nil?
unless self.class.check_restricted(self.path)
setting = self.file_manager_setting
setting = FileManagerRoot.first if setting.nil?
self.file_manager_trash = FileManagerTrash.create(:record_only=>record_only,:file_manager_upload=>self,:is_file=>is_file,:user_id=>override_user_id,:module=>self.module,:path=>self.path,:file_manager_setting=>setting,:is_slave=>is_slave, :trash_path=>trash_path)
self.update(:is_trash => true)
if record_only
other_records = self.class.where(:path=>self.path,:id.ne=>self.id,:is_trash=>false).order_by(:version=>-1).to_a
if other_records.count != 0
newest_record = other_records[0]
if self.is_default
newest_record.update_path(self.path, true)
end
newest_record
else
nil
end
end
else
puts "Path: #{path}"
puts "File or Directory restricted!"
end
end
else
self.delete_file_permanent
end
end
def delete_file_permanent(only_self=false)
if only_self
real_path = self.get_real_path
FileManagerTrash.delete_file_permanent(real_path, nil, true)
unless self.is_default
dir = File.dirname(real_path)
if Dir.entries(dir).empty?
FileUtils.rm_rf(dir)
end
end
related = self.get_related
if self.is_default
other_records = self.class.where(:file_manager_setting=>self.file_manager_setting,:path=>self.path,:id.ne=>self.id).order_by(:version=>-1)
if other_records.count != 0
newest_record = other_records[0]
real_old_path = newest_record.get_real_path(self.path)
newest_record.update_path(self.path, true)
dir = File.dirname(real_old_path)
if (Dir.entries(dir) - ['.','..']).empty?
FileUtils.rm_rf(dir)
end
self.update(:is_default => false)
end
end
self.destroy
else
FileManagerTrash.delete_file_permanent(self.path)
self.class.where(:file_manager_setting=>self.file_manager_setting,:path=>self.path,:is_trash=>false).destroy
end
end
def get_real_path(tmp_path=self.path, override_default=self.is_default)
return (override_default ? tmp_path : "#{File.dirname(tmp_path)}/.versions/v#{self.version}/#{File.basename(tmp_path)}#{self.is_file ? '' : '/'}")
end
def update_path(new_path, override_default = nil, move_to_old_version=false, copy_mode=false)
override_default = self.is_default if override_default.nil?
real_old_path = self.get_real_path(self.path)
if move_to_old_version
real_new_path = self.get_real_path(new_path, false)
self.is_default = false
else
self.is_default = override_default
real_new_path = self.get_real_path(new_path, override_default)
end
if File.exist?(real_old_path)
dir = File.dirname(real_new_path)
FileUtils.mkdir_p(dir)
if real_old_path != real_new_path
FileUtils.mv(real_old_path, real_new_path)
end
end
self.path = new_path
self.save
if self.is_default && self.related_id.present?
m = self.model.constantize
m.where(:id=>self.related_id).update_all(self.related_field => self.filename)
end
self
end
def remove_file_callback
m = self.model.constantize
if FileManagerSetting.file_only_models.include?(self.model)
m.where(:id=>self.related_id).to_a.each do |record|
record.instance_variable_set(:@skip_remove_callback, true)
record.destroy
end
else
m.where(:id=>self.related_id).update_all(self.related_field => nil)
end
end
end

View File

@ -0,0 +1,95 @@
<h3><%= generate_breadcrumb %></h3>
<section id="file_manager_section">
<span class="btn-wrapper">
<% if !@upload_item.is_trash && @all_uploads && @all_uploads.count > 1 %>
<%= link_to "javascript:void(0)", class: 'delete_file', data: { "tmp-confirm"=> "Are you sure you want to delete `#{URI.decode(@relative_path) + ' - Version ' + @active_version.to_s}`?", "link"=> admin_file_manager_path(@upload_item.id, :type=> 'upload'), "version"=> @active_version } do %>
<%= fa_icon 'trash' %> <%= t("file_manager.delete") %>
<% end %>
<% end %>
<%= link_to "/xhr/file_manager_download?id=#{@upload_item.id}", class: 'download' do %>
<%= fa_icon 'download' %> Download
<% end %>
<% if !@upload_item.is_trash %>
<% if !@is_img && !@too_big && !@unknown_encoding %>
<%= link_to "javascript:void(0)", class: 'edit' do %>
<%= fa_icon 'edit' %> Edit
<% end %>
<%= link_to "javascript:void(0)", class: 'save hide', data: {id: @upload_item.id.to_s} do %>
<%= fa_icon 'save' %> Save
<% end %>
<% end %>
<div class="dropup upload-button">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">
<i class="icon-upload-alt icon-white"></i><%= t('file_manager.upload') %>
<span class="caret"></span>
</button>
<div class="dropdown-menu upload-box">
<form action="<%= upload_file_admin_file_manager_path(@upload_item.id) %>" method="post" enctype="multipart/form-data" class="upload_form">
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
<input type="file" name="file" >
<input type="hidden" name="url" value="<%= request.fullpath %>">
<input type="hidden" name="type" value="update">
<button class="btn btn-primary" type="submit"><%= t(:submit) %></button>
</form>
</div>
</div>
<% end %>
</span>
<% if @all_uploads && @all_uploads.count > 1 %>
<ul class="tab_nav custom_tabs" style="clear: left;">
<% @all_uploads.each do |r| %>
<li class="<%= 'active' if r.version == @active_version %>"><a title="Version <%= r.version %> (<%= r.created_at.strftime('%Y/%m/%d %H:%M') %>)" class="change_version" data-version="<%= r.version %>" data-id="<%= r.id.to_s %>">Version <%= r.version %></a></li>
<% end %>
</ul>
<% end %>
<div style="clear: both;">
<% if @is_img %>
<div style="clear: both;"></div>
<img src="/xhr/file_manager_download?id=<%=@id%>" />
<% elsif File.extname(@upload_item.path) == '.pdf' %>
<iframe src="/xhr/file_manager_download?id=<%=@id%>&preview=true" style="width: 100%; height: 100vw;" />
<% elsif @too_big %>
<pre><%= @file %></pre>
<% else %>
<textarea class="edit-area hide"></textarea>
<pre><%= @file %></pre>
<% end %>
</div>
<script>
window.recycle_bin_text = "<%=t('file_manager.recycle_bin')%>";
window.current_path = "<%= @relative_path %>"
<% if @only_select_folder %>
window.only_select_folder = true;
<% end %>
<% if @setting %>
window.explorer_setting_id = "<%= @setting.id %>";
<% end %>
<% unless @disable_path_traversal %>
window.path_mode = "params";
window.use_params_request = true;
<% end %>
if (typeof(FormData) != typeof(undefined)) {
$('.upload_form').submit(function(evt){
evt.preventDefault();
var formData = new FormData(this);
formData.append('remote', true);
$.ajax({
url: $(this).attr('action'),
type: 'POST',
data: formData,
async: false,
cache: false,
contentType: false,
enctype: 'multipart/form-data',
processData: false,
success: function (data) {
reload_page(undefined, false, {"file_id": data.id});
}
});
return false;
});
}
</script>
</section>

View File

@ -0,0 +1,22 @@
<div class="path_setting_block">
<span class="remove_btn<%= '_old' unless form_setting.new_record?%>">X<%= f.hidden_field :_destroy, :class=>"should_destroy"%></span>
<h4>
<%= t('file_manager.setting', :num=> (form_setting.new_record? ? 'settings_count' : "#{i + 1}")) %>
</h4>
<div class="control-group">
<%= f.label :only_editable_for_uploader, t("file_manager.only_editable_for_uploader") , :class=>"control-label" %>
<div class="controls">
<%= f.check_box :only_editable_for_uploader %>
</div>
</div>
<div class="control-group">
<%= f.label :root_path, t("file_manager.root_path") , :class=>"control-label" %>
<div class="controls">
<%= f.text_field :root_path %>
<button class="browse_path btn btn-primary" type="button"><%=t("file_manager.browse")%></button>
</div>
</div>
<% unless form_setting.new_record? %>
<%= f.hidden_field :id %>
<% end %>
</div>

View File

@ -0,0 +1,152 @@
<% if @module_key == 'asset' %>
<style type="text/css">
.file-input.file-input-ajax-new {
display: block;
}
</style>
<% end %>
<h3><%= generate_breadcrumb %></h3>
<span class="btn-wrapper">
<a href="javascript:void(0)" class="download redirect_recycle_bin"><%=t('file_manager.recycle_bin')%></a>
</span>
<table>
<thead>
<tr>
<% if @display_module_name %>
<th><%= t("file_manager.module") %></th>
<% end %>
<th><%= t("file_manager.name") %></th>
<th><%= t("file_manager.size") %></th>
<th><%= t("file_manager.updated_at") %></th>
<% unless @only_select_folder %>
<th><%= t("file_manager.rename") %></th>
<th><%= t("file_manager.delete") %></th>
<th><%= t("download") %></th>
<th><%= t("file_manager.upload") %></th>
<th><%= t("file_manager.link") %></th>
<% end %>
</tr>
</thead>
<tbody>
<% @directory.each do |entry| %>
<% next if @relative_path.blank? && entry[:relative] == '..' %>
<tr>
<% if @display_module_name %>
<td class="module">
<%
module_info = @module_info_dict[entry[:module]]
module_title = (module_info ? module_info[:title] : nil)
%>
<% if module_title %>
<%= link_to(module_title, "javascript:void(0)", class: "module-key", data: {'key'=>entry[:module]}) %>
<% end %>
</td>
<% end %>
<td class="name">
<%= link_to "javascript:void(0)", class: entry[:type].to_s, data: {'entry'=>entry[:entry], 'id'=> entry[:id]} do %>
<%= fa_icon (entry[:type] == :file ? 'file' : 'folder') %>
<span class="filename"><%= entry[:entry] %></span>
<% end %>
</td>
<td class="size">
<%= entry[:size] %>
</td>
<td class="date">
<%= entry[:date] %>
</td>
<% unless @only_select_folder %>
<td>
<% if entry[:is_editable] && entry[:entry] != './' && entry[:entry] != '../' %>
<%= link_to 'javascript:void(0)', class: 'rename', onclick: "rename('#{entry[:id]}', '#{URI.decode(entry[:relative])}')" do %>
<%= fa_icon 'i-cursor' %> <%= t("file_manager.rename") %>
<% end %>
<% end %>
</td>
<td>
<% if entry[:is_editable] && entry[:entry] != './' && entry[:entry] != '../' %>
<%= link_to "javascript:void(0)", class: 'delete', data: { "tmp-confirm"=> "Are you sure you want to delete `#{URI.decode(entry[:relative])}`?", "link"=> admin_file_manager_path(entry[:id]) } do %>
<%= fa_icon 'trash' %> <%= t("file_manager.delete") %>
<% end %>
<% end %>
</td>
<td>
<% if entry[:id] && entry[:is_file] %>
<a class="download" href="/xhr/file_manager_download?id=<%=entry[:id]%>" title="<%= t("download") + ' ' + entry[:entry] %>" target="_blank"><%= fa_icon 'download' %> <%= t("download") %></a>
<% end %>
</td>
<td>
<div class="dropup upload-button">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">
<i class="icon-upload-alt icon-white"></i><%= t('file_manager.upload') %>
<span class="caret"></span>
</button>
<div class="dropdown-menu upload-box">
<form action="<%= upload_file_admin_file_manager_path(entry[:id]) %>" method="post" enctype="multipart/form-data" class="upload_form">
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
<input type="file" name="file" >
<input type="hidden" name="url" value="<%= request.fullpath %>">
<input type="hidden" name="type" value="update">
<button class="btn btn-primary" type="submit"><%= t(:submit) %></button>
</form>
</div>
</div>
</td>
<td>
<div class="tooltip">
<button class="copyButton btn btn-success" class="input-group-addon btn" data-link="<%= entry[:absolute].sub('public', '') %>" data-prompt="<%= t("file_manager.copied") %>">
<span class="tooltiptext"><%= t("file_manager.copy_link") %></span>
<i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
<%=
content_tag :div, class: "bottomnav clearfix" do
content_tag(:div, paginate(@uploads), class: "pagination pagination-centered")
end
%>
</section>
<script>
<% unless @disable_path_traversal %>
window.path_mode = "params";
window.use_params_request = true;
<% end %>
if (typeof(FormData) != typeof(undefined)) {
$('.upload_form').submit(function(evt){
evt.preventDefault();
var formData = new FormData(this);
formData.append('remote', true);
var tr = $(this).parents('tr').eq(0);
var file_link = tr.find('td.name a');
var size_td = tr.find('td.size');
var date_td = tr.find('td.date');
$.ajax({
url: $(this).attr('action'),
type: 'POST',
data: formData,
async: false,
cache: false,
contentType: false,
enctype: 'multipart/form-data',
processData: false,
success: function (data) {
file_link.attr('data-entry', data.filename);
file_link.data('entry', data.filename);
file_link.attr('data-id', data.id);
file_link.data('id', data.id);
file_link.find('.filename').text(data.filename);
size_td.text(data.size);
date_td.text(data.date);
}
});
return false;
});
}
</script>

View File

@ -0,0 +1,100 @@
<section id="file_manager_section">
<h3><%= generate_breadcrumb %></h3>
<span class="btn-wrapper">
<% if @directory.count != 0 %>
<a href="javascript:void(0)" class="recover_all" data-prompt="<%=t('file_manager.are_you_sure_to_recover_all')%>" data-num="<%=@directory.count%>"data-unit="<%=t('file_manager.file_unit')%>" title="<%=t('file_manager.clean_up_recycle_bin')%>">
<%=t('file_manager.recover_all')%>
</a>
<a href="javascript:void(0)" class="clean_up_recycle_bin" data-prompt="<%=t('file_manager.are_you_sure_to_delete_files_permanently')%>" data-num="<%=@directory.count%>"data-unit="<%=t('file_manager.file_unit')%>" title="<%=t('file_manager.clean_up_recycle_bin')%>">
<%=t('file_manager.clean_up_recycle_bin')%>
</a>
<% end %>
</span>
<table>
<thead>
<tr>
<% if @display_module_name %>
<th><%= t("file_manager.module") %></th>
<% end %>
<th><%= t("file_manager.name") %></th>
<th><%= t("file_manager.size") %></th>
<th><%= t("file_manager.deleted_at") %></th>
<th><%= t("file_manager.updated_at") %></th>
<% unless @only_select_folder %>
<th><%= t("file_manager.recover") %></th>
<th><%= t("file_manager.delete_permanently") %></th>
<% end %>
</tr>
</thead>
<tbody>
<% @directory.each do |entry| %>
<% next if @relative_path.blank? && entry[:relative] == '..' %>
<tr>
<% if @display_module_name %>
<td>
<%
module_info = @module_info_dict[entry[:module]]
module_title = (module_info ? module_info[:title] : nil)
%>
<% if module_title %>
<%= link_to(module_title, "javascript:void(0)", class: "module-key", data: {'key'=>entry[:module]}) %>
<% end %>
</td>
<% end %>
<td class="name">
<%= link_to "javascript:void(0)", class: entry[:type].to_s, data: {'entry'=>entry[:entry], 'id'=> entry[:upload_id]} do %>
<%= fa_icon (entry[:type] == :file ? 'file' : 'folder') %>
<span class="filename"><%= entry[:name] %></span>
<% end %>
</td>
<td>
<%= entry[:size] %>
</td>
<td class="date">
<%= entry[:deleted_at] %>
</td>
<td class="date">
<%= entry[:date] %>
</td>
<% unless @only_select_folder %>
<td>
<% if entry[:entry] != './' && entry[:entry] != '../' %>
<%= link_to 'javascript:void(0)', class: 'recover', data: { "tmp-confirm"=> t("file_manager.are_you_sure_to_recover"), "link"=> recover_admin_file_manager_path(entry[:id]) } do %>
<%= fa_icon 'trash-restore' %> <%= t("file_manager.recover") %>
<% end %>
<% end %>
</td>
<td>
<% if entry[:entry] != './' && entry[:entry] != '../' %>
<%= link_to "javascript:void(0)", class: 'delete', data: { "tmp-confirm"=> t("file_manager.are_you_sure_you_want_to_delete_file_permanently",:file=>entry[:org_path]), "link"=> admin_file_manager_path(entry[:id], permanent: true) } do %>
<%= fa_icon 'trash' %> <%= t("file_manager.delete_permanently") %>
<% end %>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</section>
<div class="bottomnav clearfix">
<%= content_tag(:div, paginate(@trashes), class: "pagination pagination-centered") %>
<div class="pull-right">
<% if @thread %>
<button class="show_progress btn btn-primary" type="button"><%= t("file_manager.show_progress") %></button>
<% end %>
</div>
</div>
<script>
window.is_recycle_bin = true;
</script>
<script>
$(document).ready(function(){
if($("#threadModal").data('id')){
$("#threadModal").modal("show");
$(".show_progress").click(function(){
$("#threadModal").modal("show");
})
}
})
</script>

View File

@ -0,0 +1,39 @@
<div class="box-content">
<ul style="display: flex; flex-wrap: wrap;">
<% modules_info = @setting.class.get_models_uploaders
@setting.get_modules_keys.each do |k|
if k == 'all'
info = @setting.class.get_module_info(k)
else
next if modules_info[k].keys.detect{|_model| @setting.modules_saved_info[_model.to_s]}.nil?
info = @setting.class.get_module_info(k)
next if info[:sidebar] && !display_sidebar?(info[:sidebar])
next if info.nil?
end
%>
<li>
<span>
<a href="<%= module_admin_file_manager_path(k) %>" title="<%= info[:title] %>">
<i class="<%= info[:icon_class] %> icon_big"></i>
<%= info[:title] %>
</a>
</span>
</li>
<% end %>
</ul>
</div>
<style type="text/css">
i.icon_big{
font-size: 3em;
display: block;
}
#main-wrap li{
font-size: 1.3em;
list-style: none;
float: left;
margin: 0.8em;
}
#main-wrap a{
color: black;
}
</style>

View File

@ -0,0 +1,90 @@
<% content_for :page_specific_css do %>
<%= stylesheet_link_tag 'file_manager/default_theme', media: 'all' %>
<%= stylesheet_link_tag 'file_manager/application', media: 'all' %>
<%= stylesheet_link_tag "bootstrap-fileinput/css/fileinput.min" %>
<%= stylesheet_link_tag "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.min.css", :crossorigin => "anonymous" %>
<% end %>
<% content_for :page_specific_javascript do %>
<%= javascript_include_tag 'file_manager/application' %>
<%= javascript_include_tag "file-upload/tmpl.min.js" %>
<%= javascript_include_tag "file-upload/load-image.min.js" %>
<%= javascript_include_tag "file-upload/canvas-to-blob.min.js" %>
<%= javascript_include_tag "file-upload/jquery.iframe-transport.js" %>
<%= javascript_include_tag "file-upload/drop-zone.js" %>
<%= javascript_include_tag "bootstrap-fileinput/js/plugins/piexif.min" %>
<%= javascript_include_tag "bootstrap-fileinput/js/plugins/sortable.min" %>
<%= javascript_include_tag "bootstrap-fileinput/js/fileinput.min" %>
<%= javascript_include_tag "bootstrap-fileinput/js/locales/#{I18n.locale}" %>
<% end %>
<div id="threadModal" class="modal hide fade in" tabindex="-1" role="dialog" aria-labelledby="threadModal" aria-hidden="false" data-id="<%= @thread.id if @thread %>">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h3><%= @thread_title %></h3>
</div>
<div class="modal-body">
<% if @thread %>
<div class="thread-status"><%= @thread.status[:status] %></div>
<div><span class="thread-current-count"><%= @thread.status[:current_count].to_i %></span>/<span class="thread-all-count"><%= @thread.status[:all_count].to_i %></span></div>
<span class="thread-finish_percent"><%= @thread.status[:finish_percent].to_i %></span> % finished
<% else %>
<div class="thread-status">Processing</div>
<div><span class="thread-current-count">0</span>/<span class="thread-all-count">0</span></div>
<span class="thread-finish_percent">0</span> % finished
<% end %>
</div>
<div class="modal-footer">
<button class="btn" id="modal-close-btn" style="width: 4em;" data-dismiss="modal" aria-hidden="true"><%=t('close')%></button>
</div>
</div>
</div>
</div>
<%= render_filter @filter_fields, "index_table" %>
<section id="file_manager_section">
<% if @display_uploader_region %>
<input id="file-uploader" type="file" name="file" multiple>
<style type="text/css">
.file-input.file-input-ajax-new {
display: none;
}
</style>
<script class="file-uploader-script">
var $el;
$(document).ready(function() {
if($('#file_manager_section .file-input').length == 0){
$el = $("#file-uploader");
var footerTemplate = '<div class="file-thumbnail-footer" style ="height:94px">\n' +
' <div>{caption}</div> ' +
' <div class="small" style="margin:15px 0 2px 0">{size}</div> {progress}\n{indicator}\n{actions}\n' +
'</div>';
$el.fileinput({
language: "<%= I18n.locale %>",
uploadUrl: "<%= upload_file_admin_file_manager_path(@module_key) %>",
previewFileType: 'any',
uploadAsync: true,
layoutTemplates: {footer: footerTemplate}
}).on('filebatchselected', function(event, files) {
//console.log(files);
}).on('fileuploaded', function(event, data) {
reload_explorer_section(undefined, $('#file_manager_section .file-input .progress-bar:not(.progress-bar-success)').length != 0);
}).on('filebatchuploadcomplete', function(event, data) {
reload_explorer_section();
});
}
});
</script>
<% end %>
<script>
window.file_manager_path = "<%= admin_file_managers_path(:locale=> I18n.locale) %>";
window.module_key = "<%= @module_key %>";
</script>
<span id="index_table">
<% if @file %>
<%= render :partial => 'file' %>
<% elsif @recycle_bin %>
<%= render :partial => 'recycle_bin' %>
<% else %>
<%= render :partial => 'module' %>
<% end %>
</span>

View File

@ -0,0 +1,106 @@
<%= stylesheet_link_tag 'file_manager/application', media: 'all' %>
<%= javascript_include_tag 'file_manager/file_manager' %>
<style>
.path_setting_block{
border: 1px solid black;
padding: 1em;
position: relative;
}
.remove_btn_old, .remove_btn{
color: red;
float: right;
position: absolute;
right: 0;
top: 0;
background: #ffc3c3;
padding: 0.1em 0.5em;
cursor: pointer;
}
#browse_model .modal-body{
font-size: 1rem;
}
</style>
<div class="modal hide fade in" id="browse_model" tabindex="-1" role="dialog" data-backdrop="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<a class="close" data-dismiss="modal">×</a>
<h3><%= t("file_manager.select_folder") %></h3>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<a href="#" class="btn" data-dismiss="modal"><%= t("file_manager.select_current_folder") %></a>
</div>
</div>
</div>
</div>
<h3><%= t('file_manager.path_setting') %></h3>
<%= form_for( @root, url: admin_file_managers_update_settings_path, html: {class: 'form-horizontal'} ) do |f| %>
<% if current_user.is_admin? %>
<div class="control-group">
<%= f.label :disable_path_traversal, t("file_manager.disable_path_traversal") , :class=>"control-label" %>
<div class="controls">
<%= f.check_box :disable_path_traversal %>
</div>
</div>
<% end %>
<% @root.file_manager_settings.each_with_index do |setting, i| %>
<%= f.fields_for :file_manager_settings, setting do |f| %>
<%= render :partial => 'form_setting', object: f.object, locals: {f: f, i: i} %>
<% end %>
<% end %>
<a id="">
</a>
<!-- Add -->
<div class="add-target">
</div>
<p class="add-btn">
<%= hidden_field_tag 'file_manager_settings_count', @root.file_manager_settings.count %>
<a id="add_file_manager_setting" class="trigger btn btn-small btn-primary"><i class="icons-plus"></i> <%= t(:add) %></a>
</p>
<!-- Form Actions -->
<div class="form-actions">
<%= get_referer_url[:action] rescue "" %>
<%= f.submit t('submit'), class: 'btn btn-primary' %>
<input type="hidden" name="referer_url" value="<%= get_referer_url %>">
</div>
<script>
$(document).on('click', '#add_file_manager_setting', function(){
var new_id = $(this).prev().attr('value');
var old_id = new RegExp("new_file_manager_settings", "g");
$(this).prev().attr('value', parseInt(new_id) + 1);
$(this).parent().siblings('.add-target').append(("<%= escape_javascript(add_attribute 'form_setting', f, :file_manager_settings) %>").replace(old_id, new_id).replace('settings_count', $('.path_setting_block').length + 1));
})
$(document).on('click', '.remove_btn_old', function(){
if(window.confirm('<%=t('file_manager.are_you_sure_to_delete')%>')){
$(this).children('.should_destroy').attr('value', 1);
$(this).parent().hide();
}
})
$(document).on('click', '.remove_btn', function(){
if(window.confirm('<%=t('file_manager.are_you_sure_to_delete')%>')){
$(this).parent().remove();
}
})
$(document).on('click', '.browse_path', function(){
var that = $(this)
var path = that.siblings('input').val();
window.current_browse_input = that.siblings('input');
window.current_path = path;
$.get("/xhr/file_manager/" + "?select_mode=true&path="+path).done(function(data){
window.only_select_folder = true;
$("#browse_model .modal-body").html('<h4 class="breadcrumb-index"></h4>'+ data);
$("#browse_model").modal('show');
change_breadcrumb();
}).fail(function(){
alert("No such file or directory!");
that.siblings('input').val('');
});
})
$("#browse_model").on('hidden.bs.modal', function () {
window.current_browse_input.val(window.current_path);
});
</script>
<% end %>

View File

@ -0,0 +1,9 @@
<% active_setting_id = @custom_data_field[:setting_id] || @settings.first.id rescue nil %>
<% if @settings.length != 0 %>
<div class="control-group input-content">
<label class="control-label" for="custom_setting_id"><%=t("file_manager.path_setting")%> :</label>
<div class="controls">
<%= select_tag("#{@field_name}[custom_data_field][setting_id]", options_for_select(@settings.map.with_index{|setting, i| [t("file_manager.setting",:num=>i+1),setting.id]},active_setting_id),{:id=>"custom_setting_id"})%>
</div>
</div>
<% end %>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>FileManager</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'file_manager/default_theme', media: 'all' %>
<%= stylesheet_link_tag 'file_manager/application', media: 'all' %>
<%= javascript_include_tag 'file_manager/application' %>
<%= javascript_include_tag "file-upload/tmpl.min.js" %>
<%= javascript_include_tag "file-upload/load-image.min.js" %>
<%= javascript_include_tag "file-upload/canvas-to-blob.min.js" %>
<%= javascript_include_tag "file-upload/jquery.iframe-transport.js" %>
<%= javascript_include_tag "file-upload/drop-zone.js" %>
<%= stylesheet_link_tag "bootstrap-fileinput/css/fileinput.min" %>
<%= javascript_include_tag "bootstrap-fileinput/js/plugins/piexif.min" %>
<%= javascript_include_tag "bootstrap-fileinput/js/plugins/sortable.min" %>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<%= javascript_include_tag "bootstrap-fileinput/js/fileinput.min" %>
<%= javascript_include_tag "bootstrap-fileinput/js/locales/#{I18n.locale}" %>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.min.css" crossorigin="anonymous">
</head>
<body id="file_manager_body">
<div id="threadModal" class="modal hide fade in" tabindex="-1" role="dialog" aria-labelledby="threadModal" aria-hidden="false" data-id="<%= @thread.id if @thread %>">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h3><%=t("file_manager.clean_up_recycle_bin")%></h3>
</div>
<div class="modal-body">
<% if @thread %>
<div class="thread-status"><%= @thread.status[:status] %></div>
<div><span class="thread-current-count"><%= @thread.status[:current_count].to_i %></span>/<span class="thread-all-count"><%= @thread.status[:all_count].to_i %></span></div>
<span class="thread-finish_percent"><%= @thread.status[:finish_percent].to_i %></span> % finished
<% else %>
<div class="thread-status">Processing</div>
<div><span class="thread-current-count">0</span>/<span class="thread-all-count">0</span></div>
<span class="thread-finish_percent">0</span> % finished
<% end %>
</div>
<div class="modal-footer">
<button class="btn" id="modal-close-btn" style="width: 4em;" data-dismiss="modal" aria-hidden="true"><%=t('close')%></button>
</div>
</div>
</div>
</div>
<h3 class="breadcrumb-index">
<span id="breadcrumb-relative-url">
<%= generate_breadcrumb(@relative_path) %>
</span>
<div id="breadcrumb-editable" contenteditable="" class="hide">/</div>
</h3>
<%= yield %>
</body>
</html>

18
bin/rails Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/file_manager/engine', __FILE__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
# require 'rails/all'
# require 'rails/engine/commands'
require "action_controller/railtie"
require "action_mailer/railtie"
require "sprockets/railtie"
require "rails/test_unit/railtie"
require 'rails/engine/commands'
require "mongoid/railtie"

41
config/locales/en.yml Normal file
View File

@ -0,0 +1,41 @@
en:
module_name:
file_manager: File Manager
file_manager:
module: "Module"
all_files: "All Files"
root: "root"
file_unit: ""
only_editable_for_uploader: Only editable for Uploader
root_path: Root Path
path_setting: Path Setting
file_manager: File Manager
settings: Settings
create_folder: Create Folder
new_folder: New Folder
setting: "Setting %{num}"
are_you_sure_to_recover: "Are you sure to recover file?"
are_you_sure_to_delete: "Are you sure to delete?"
are_you_sure_to_recover_all: "Are you sure to recover ${num} files?"
are_you_sure_to_delete_files_permanently: "Are you sure to delete ${num} files permanently?"
are_you_sure_you_want_to_delete_file_permanently: "Are you sure you want to delete `%{file}` permanently?"
browse: "Browse"
select_folder: "Select Folder"
select_current_folder: "Select Current Folder"
disable_path_traversal: "Disable path traversal"
upload: "Upload"
delete: "Delete"
rename: "Rename"
recycle_bin: "Recycle Bin"
delete_permanently: "Delete permanently"
recover: "Recover"
recover_all: "Recover All"
name: "Name"
size: "Size"
original_path: "Original Path"
updated_at: "Updated at"
deleted_at: "Deleted at"
clean_up_recycle_bin: "Clean Up Recycle Bin"
link: "Link"
copy_link: "Click to copy"
copied: "Copied"

41
config/locales/zh_tw.yml Normal file
View File

@ -0,0 +1,41 @@
zh_tw:
module_name:
file_manager: 檔案管理
file_manager:
module: "模組"
all_files: "全部檔案"
root: "根目錄"
file_unit: "個"
only_editable_for_uploader: 僅供上傳者編輯
root_path: 根路徑
path_setting: 路徑設定
file_manager: 檔案管理
settings: 設定
create_folder: 創建資料夾
new_folder: 新增資料夾
setting: "設定%{num}"
are_you_sure_to_recover: "你確定要復原檔案嗎?"
are_you_sure_to_delete: "你確定要刪除嗎?"
are_you_sure_to_recover_all: "你確定要還原這${num}檔案嗎?"
are_you_sure_to_delete_files_permanently: "你確定要永久刪除這${num}檔案嗎?"
are_you_sure_you_want_to_delete_file_permanently: "你確定要永久刪除`%{file}`?"
browse: "瀏覽"
select_folder: "選擇資料夾"
select_current_folder: "選擇當前資料夾"
disable_path_traversal: "禁止使用者訪問其他路徑"
upload: "上傳"
delete: "刪除"
rename: "重新命名"
recycle_bin: "資源回收桶"
delete_permanently: "永久刪除"
recover: "復原"
recover_all: "還原所有項目"
name: "名稱"
size: "大小"
original_path: "原始位置"
updated_at: "修改日期"
deleted_at: "刪除日期"
clean_up_recycle_bin: "清理資源回收桶"
link: "連結"
copy_link: "複製連結"
copied: "已複製"

53
config/routes.rb Normal file
View File

@ -0,0 +1,53 @@
Rails.application.routes.draw do
if ENV['worker_num']=='0' && File.basename($0) != 'rake' && !Rails.const_defined?('Console')
count = FileManagerRoot.count
if count == 0
root = FileManagerRoot.create
else
if count > 1
FileManagerRoot.all[1..-1].each{|f| f.destroy}
end
root = FileManagerRoot.first
end
FileManagerUpload.create_indexes
FileManagerTrash.create_indexes
setting = root.file_manager_settings.first
if setting.nil?
setting = root.file_manager_settings.create
end
Thread.new do
setting.create_records
end
end
locales = Site.first.in_use_locales rescue I18n.available_locales
scope "(:locale)", locale: Regexp.new(locales.join("|")) do
namespace :admin do
resources :file_managers do
collection do
get :settings
patch :update_settings
end
member do
get :module
put :rename
post :recover
post :save
post :recover_all
post :clean_trashes
post :get_trash_count
post :upload_file
end
end
end
get '/xhr/file_manager_download', to: 'file_managers#download'
get '/xhr/file_manager', to: 'file_managers#index_backend'
post '/xhr/file_manager_upload', to: 'file_managers#upload_index'
post "/xhr/file_manager_create_folder/(:path)", to: 'file_managers#create_folder', constraints: { path: /.*/ }
post "/xhr/file_manager_save/(:path)", to: 'file_managers#save', constraints: { path: /.*/ }
get '/xhr/file_manager/:path', to: 'file_managers#path', constraints: { path: /.+/ }
put '/xhr/file_manager/(:path)', to: 'file_managers#rename', constraints: { path: /.+/ }
post '/xhr/file_manager_upload/:path', to: 'file_managers#upload', constraints: { path: /.+/ }
delete '/xhr/file_manager/(:path)', to: 'file_managers#delete', constraints: { path: /.+/ }
end
end

106
file-manager.gemspec Normal file
View File

@ -0,0 +1,106 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require "file_manager/version"
bundle_update_flag = ARGV[0]=='update' || ARGV[0]=='install'
if bundle_update_flag
require "json"
env_pwd = ENV['PWD']
begin
require ::File.expand_path('app/helpers/bundler_helper.rb', env_pwd)
extend BundlerHelper
rescue LoadError
def bundler_with_clean_env
if block_given?
if Bundler.respond_to?(:with_unbundled_env)
Bundler.with_unbundled_env(&Proc.new)
else
Bundler.with_clean_env(&Proc.new)
end
end
end
end
app_path = File.expand_path(__dir__)
if Dir.exist?("#{app_path}/modules")
template_path = env_pwd + '/app/templates'
all_template = Dir.glob(template_path+'/*/')
default_file_manager_widget_info = JSON.parse(File.read("#{app_path}/modules/file-manager/info.json"))["widgets"].sort_by{|h| h["filename"].to_i} rescue []
all_template.each do |folder|
if !folder.include?('mobile')
info_json_file = "#{folder}modules/file-manager/info.json"
if File.exist?(info_json_file)
#bundler_with_clean_env{system ('cp -f '+ app_path + '/modules/file-manager/show.html.erb ' + "#{folder}modules/file-manager/.")}
begin
file_text = File.read(info_json_file) rescue ""
encode_file_text = file_text.encode("UTF-8", "UTF-8", invalid: :replace, replace: "???")
next if (encode_file_text.include?("???") rescue true)
info = JSON.parse(encode_file_text) rescue {}
flag = (info.count != 0 rescue false)
if flag
puts "Checking file-manager widgets"
widget_info = info["widgets"].sort_by{|h| h["filename"].to_i} rescue []
update_flag = false
last_index = widget_info[-1]["filename"].match(/\d+/)[0].to_i rescue nil
if !last_index.nil?
idx_regex = /^(\d+[\. \t]*)|[ \t]+$/
default_file_manager_widget_info.each do |h|
name_without_index = h["name"]["zh_tw"].gsub(idx_regex,'')
widget_info_index = (widget_info.index{|hh| hh["name"]["zh_tw"].gsub(idx_regex,'') == name_without_index}||-1 rescue -1)
if widget_info_index == -1
update_flag = true
copy_h = h.dup
h.delete("force_cover")
last_index = last_index + 1
copy_h["filename"] = copy_h["filename"].sub(/\d+/){|ff| last_index.to_s}
copy_h["name"].keys.each do |locale|
copy_h["name"][locale] = copy_h["name"][locale].sub(/\d+/){|ff| last_index.to_s}
end
widget_info << copy_h
bundler_with_clean_env{%x[cp -f #{app_path}/modules/file-manager/_#{h["filename"]}.html.erb #{folder}modules/file-manager/_#{copy_h["filename"]}.html.erb]}
elsif h["force_cover"] == "true"
bundler_with_clean_env{%x[cp -f #{app_path}/modules/file-manager/_#{h["filename"]}.html.erb #{folder}modules/file-manager/_#{widget_info[widget_info_index]["filename"]}.html.erb]}
end
end
if update_flag
info["widgets"] = widget_info
puts "Writing json #{info["widgets"].count} in #{info_json_file}"
begin
info_json = JSON.pretty_generate(info).gsub(":[",":[\n").gsub(":{",":{\n")
rescue
info_json = info.to_s.gsub("=>",": \n")
end
File.open(info_json_file,"w+"){|f| f.write(info_json)}
end
end
end
rescue => e
puts e
puts "There has some error when checking file-manager widgets"
end
else
if !Dir.exist?(File.dirname(info_json_file)) && Dir.exist?(File.dirname(File.dirname(info_json_file)))
bundler_with_clean_env{system ('cp -r '+ app_path + '/modules/ ' + folder)}
end
end
end
end
end
end
Gem::Specification.new do |s|
s.name = "file-manager"
s.version = FileManager::VERSION
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib"]
s.authors = ["Bohung Chiu"]
s.date = "2024-05-12"
s.description = "file-manager plugin."
s.email = ["bohung@rulingcom.com"]
s.files = ["{app,config,lib}/**/*", "MIT-LICENSE", "README.rdoc", "Rakefile"]
s.homepage = "https://orbitek.co"
s.licenses = ["MIT"]
s.summary = "file-manager plugin."
s.test_files = ["test/test_helper.rb", "test/controllers/index_test.rb"]
# s.add_dependency "charlock_holmes"
# s.add_dependency "custom_gallery"
end

4
lib/file-manager.rb Normal file
View File

@ -0,0 +1,4 @@
require "file_manager/engine"
module FileManager
end

View File

@ -0,0 +1,29 @@
module FileManager
class Engine < ::Rails::Engine
initializer "file_manager" do
Rails.application.config.to_prepare do
FileManagerSetting.get_models_uploaders
OrbitApp.registration "FileManager", :type => "ModuleApp" do
module_label "file_manager.file_manager"
base_url File.expand_path File.dirname(__FILE__)
# widget_methods []
# widget_settings [{"enable_custom_widget_data"=>true}]
authorizable
# frontend_enabled
side_bar do
head_label_i18n 'file_manager.file_manager', icon_class: "icon-folder-open"
available_for "managers"
active_for_controllers (['admin/file_managers'])
head_link_path "admin_file_managers_path"
context_link 'file_manager.file_manager',
:link_path=>"admin_file_managers_path" ,
:priority=>1,
:active_for_action=>{'admin/file_managers'=>'index'},
:available_for => 'managers'
end
end
end
end
end
end

View File

@ -0,0 +1,3 @@
module FileManager
VERSION = "0.0.1"
end

0
lib/tasks/.keep Normal file
View File

View File

@ -0,0 +1,8 @@
desc 'Remove duplicated bulletins (announcements) created by preview'
namespace :bulletin do
task :remove_preview_bulletins => [:environment] do
bulletins = Bulletin.where(is_preview: true)
bulletins.destroy_all
end
end

0
test/controllers/.keep Normal file
View File

View File

@ -0,0 +1,62 @@
require 'test_helper'
class IndexTest < ActionDispatch::IntegrationTest
def test_success_root
get '/'
assert_response :success
assert_template :index
directory = assigns(:directory)
assert_equal Dir.entries('.').sort, directory.map { |d| d[:relative] }.sort
end
def test_success_file
filename = 'Gemfile'
get "/#{filename}"
assert_response :success
assert_template :file
assert_equal File.read(filename), assigns(:file)
end
def test_out_of_directory_access
assert_raise(ArgumentError) do
get "/../../test"
end
end
def test_move
old_filename = 'tmp/myfile'
new_filename = 'tmp/movedfile'
FileUtils.touch old_filename
put "/#{old_filename}", params: { new_name: new_filename }
assert_response :success
assert File.exists?(new_filename)
assert !File.exists?(old_filename)
ensure
FileUtils.rm_rf old_filename
FileUtils.rm_rf new_filename
end
def test_delete_file
file_to_delete = 'tmp/new_file_to_delete'
FileUtils.touch file_to_delete
assert File.exists?(file_to_delete)
delete "/#{file_to_delete}"
assert_response :success
assert !File.exists?(file_to_delete)
end
def test_upload
filename = 'Gemfile'
uploaded_filename = "tmp/#{filename}"
assert !File.exists?(uploaded_filename)
post '/tmp/', params: { file: Rack::Test::UploadedFile.new(filename, 'text/plain') }
assert File.exists?(uploaded_filename)
ensure
FileUtils.rm_rf uploaded_filename
end
end

0
test/fixtures/.keep vendored Normal file
View File

0
test/fixtures/files/.keep vendored Normal file
View File

0
test/helpers/.keep Normal file
View File

0
test/integration/.keep Normal file
View File

7
test/test_helper.rb Normal file
View File

@ -0,0 +1,7 @@
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
# Add more helper methods to be used by all tests here...
end