amcms_filemanager 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +78 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/amcms_filemanager_manifest.js +1 -0
  6. data/app/assets/images/amcms_filemanager/folder.png +0 -0
  7. data/app/assets/stylesheets/amcms_filemanager/application.css +34 -0
  8. data/app/assets/stylesheets/amcms_filemanager/filemanager.css +4 -0
  9. data/app/controllers/amcms_filemanager/application_controller.rb +11 -0
  10. data/app/controllers/amcms_filemanager/filemanager_controller.rb +93 -0
  11. data/app/controllers/amcms_filemanager/imagemanager_controller.rb +34 -0
  12. data/app/helpers/amcms_filemanager/application_helper.rb +4 -0
  13. data/app/helpers/amcms_filemanager/filemanager_helper.rb +4 -0
  14. data/app/jobs/amcms_filemanager/application_job.rb +4 -0
  15. data/app/lib/amcms_filemanager/filemanager.rb +181 -0
  16. data/app/lib/amcms_filemanager/imagemanager.rb +5 -0
  17. data/app/mailers/amcms_filemanager/application_mailer.rb +6 -0
  18. data/app/models/amcms_filemanager/application_record.rb +5 -0
  19. data/app/uploaders/amcms_filemanager/filemanager_uploader.rb +56 -0
  20. data/app/views/amcms_filemanager/filemanager/index.html.erb +168 -0
  21. data/app/views/layouts/amcms_filemanager/application.html.erb +497 -0
  22. data/config/locales/en.yml +4 -0
  23. data/config/routes.rb +12 -0
  24. data/lib/amcms_filemanager/configuration.rb +9 -0
  25. data/lib/amcms_filemanager/engine.rb +25 -0
  26. data/lib/amcms_filemanager/version.rb +3 -0
  27. data/lib/amcms_filemanager.rb +6 -0
  28. data/lib/tasks/amcms_filemanager_tasks.rake +4 -0
  29. metadata +127 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8b9335c2a48570995936b16c7fc56963c24b2c569fc4fd803f65259dfb1795c9
4
+ data.tar.gz: e8e10a7e5abf00a38235ff32aa34b3679d35226cb3fc5265cae0dc4f45e7aba5
5
+ SHA512:
6
+ metadata.gz: e5a34dc63b0f4d87771d39e61bd872c1b5cb577e412f3569658d101ae4a5695f9da294469fa86dedcab03dfd486e50d037d4cbcac2afc7a7187fe1fd1615fffc
7
+ data.tar.gz: 811ba3c49aff61bcccb6b9738d492572e5216702625771f229fa31aa9d3f3d1fe56aba48a226327b51a5aa842e5643c1318f82d673131a9c3435fbffc9c3df8a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Andrew Maughan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # AmcmsFilemanager
2
+ Filemanager for tinymce. Allows uploading of files to a remote server via the file picker plugin in Tinymce.
3
+
4
+ ## Full Feature List
5
+ - Browse directory
6
+ - Mutli File Upload
7
+ - Rename File
8
+ - Copy File
9
+ - Delete File
10
+ - Make Directory
11
+ - Resize Image
12
+ - Authentication hooks
13
+ - Allowed file types
14
+
15
+ ## Usage
16
+ How to use my plugin.
17
+
18
+ ## Installation
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'amcms_filemanager'
23
+ ```
24
+
25
+ And then execute:
26
+ ```bash
27
+ $ bundle
28
+ ```
29
+
30
+ Or install it yourself as:
31
+ ```bash
32
+ $ gem install amcms_filemanager
33
+ ```
34
+
35
+ Mount the filemanger routes in `config/routes.rb`
36
+
37
+ ```ruby
38
+ mount AmcmsFilemanager::Engine => '/', as: :amcms_filemanager
39
+ ```
40
+
41
+ Create an initialiser in your project
42
+
43
+ `config/initialisers/amcms_filemanager.rb`
44
+
45
+
46
+ ```ruby
47
+ AmcmsFilemanager::configure do |config|
48
+ config.root_path = 'images'
49
+ config.authentication = {
50
+ model: :user
51
+ }
52
+ end
53
+ ```
54
+
55
+ In your project where TinyMCE is located add this to initialise the filemanager:
56
+ ```js
57
+ const amcmsFileManager = (type, callback) => tinymce.activeEditor.windowManager.openUrl({
58
+ title: 'AMCMS Filemanager',
59
+ url: `http://localhost:3000/filemanager?type=${type}`,
60
+ onMessage: function(instance, event) {
61
+ callback(event.data.location);
62
+ tinymce.activeEditor.windowManager.close();
63
+ },
64
+ width: window.innerWidth - 50,
65
+ height: window.innerHeight - 50,
66
+ });
67
+ ```
68
+
69
+ Next add a callback to the filepicker to trigger the filemanager for image, link and media dialogs:
70
+
71
+ ```js
72
+ tinymce.init({
73
+ ...
74
+ file_picker_callback: (callback, value, meta) => {
75
+ amcmsFileManager(meta.filetype, callback);
76
+ }
77
+ });
78
+ ```
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/amcms_filemanager .css
@@ -0,0 +1,34 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
16
+
17
+ .item-name {
18
+ overflow: hidden;
19
+ }
20
+
21
+ .fm-file-panel {
22
+ padding-top: 80px;
23
+ }
24
+
25
+ .fm-image-thumbnail {
26
+ width: 200px;
27
+ height: 200px;
28
+ object-fit: scale-down;
29
+ margin: 10px 0;
30
+ }
31
+
32
+ #image-preview-panel {
33
+ overflow-x:auto;
34
+ }
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,11 @@
1
+ module AmcmsFilemanager
2
+ class ApplicationController < ActionController::Base
3
+ before_action :authenticate
4
+
5
+ def authenticate
6
+ return unless AmcmsFilemanager.config.try(:authentication).present?
7
+
8
+ send("authenticate_#{AmcmsFilemanager.config.authentication[:model]}!")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AmcmsFilemanager
4
+ class FilemanagerController < ApplicationController
5
+ before_action :item_exists?, only: %i[mkdir rename]
6
+
7
+ def index
8
+ @config = {
9
+ routes: {
10
+ cd_path: filemanager_change_directory_path,
11
+ mkdir_path: filemanager_mkdir_path,
12
+ rm_path: filemanager_destroy_path,
13
+ rename_path: filemanager_rename_path,
14
+ copy_path: filemanager_copy_path,
15
+ image_info_path: imagemanager_path(':id'),
16
+ },
17
+ type: params.fetch(:type, 'all')
18
+ }
19
+ end
20
+
21
+ def change_directory
22
+ @files = AmcmsFilemanager::Filemanager.list(params[:cd], params[:type])
23
+
24
+ render json: {
25
+ files: @files,
26
+ pwd: pwd,
27
+ pwd_name: pwd.split('/').last,
28
+ root_directory: AmcmsFilemanager.config.root_path,
29
+ parent_directory: AmcmsFilemanager::Filemanager.parent_dir(params[:cd])
30
+ }
31
+ end
32
+
33
+ def copy
34
+ if AmcmsFilemanager::Filemanager.copy(params[:item_name])
35
+ render json: { success: true }, status: 200
36
+ else
37
+ render json: { success: false }, status: 400
38
+ end
39
+ end
40
+
41
+ def mkdir
42
+ if AmcmsFilemanager::Filemanager.mkdir(params[:pwd], params[:item_name])
43
+ render json: { success: true }, status: 200
44
+ else
45
+ render json: { success: false }, status: 400
46
+ end
47
+ end
48
+
49
+ def rename
50
+ if AmcmsFilemanager::Filemanager.rename(params[:pwd], params[:original_filename], params[:item_name])
51
+ render json: { success: true }, status: 200
52
+ else
53
+ render json: { success: false }, status: 400
54
+ end
55
+ end
56
+
57
+ def destroy
58
+ if AmcmsFilemanager::Filemanager.delete(params[:file])
59
+ render json: { success: true }, status: 200
60
+ else
61
+ render json: { success: false }, status: 400
62
+ end
63
+ end
64
+
65
+ def upload
66
+ if AmcmsFilemanager::Filemanager::upload(params[:dir], params[:file], params[:type])
67
+ render json: { success: true }, status: 200
68
+ else
69
+ render json: { success: false }, status: 400
70
+ end
71
+ rescue CarrierWave::IntegrityError => e
72
+ render json: { error: e.message }, status: 400
73
+ end
74
+
75
+ private
76
+
77
+ def item_exists?
78
+ return true unless AmcmsFilemanager::Filemanager.exists?(params[:pwd], params[:item_name])
79
+
80
+ message = {
81
+ title: 'Error',
82
+ body: 'Item already exists. Please try a different name',
83
+ status: 'danger'
84
+ }
85
+ render json: { success: false, message: message }, status: 400
86
+ false
87
+ end
88
+
89
+ def pwd
90
+ @pwd ||= params[:cd].blank? ? AmcmsFilemanager.config.root_path : params[:cd].gsub(%r{^/}, '')
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AmcmsFilemanager
4
+ class ImagemanagerController < ApplicationController
5
+ def update
6
+ uploader = AmcmsFilemanager::FilemanagerUploader.new
7
+ uploader.retrieve_from_store!(source_file)
8
+ uploader.resize_to_fit(params[:width], params[:height])
9
+ render json: { success: true }
10
+ rescue StandardError => e
11
+ render json: { success: false, message: e.message }, status: 400
12
+ end
13
+
14
+ def show
15
+ if AmcmsFilemanager::Filemanager.image?(source_file)
16
+ uploader = AmcmsFilemanager::FilemanagerUploader.new
17
+ uploader.retrieve_from_store!(source_file)
18
+ render json: { success: true, filepath: filepath, width: uploader.width, height: uploader.height, size: uploader.size }
19
+ else
20
+ render json: { success: false, error: 'File is not an image' }, status: 400
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def source_file
27
+ @source_file ||= params[:copy] ? AmcmsFilemanager::Filemanager.copy(filepath) : Rails.root.join('public', filepath)
28
+ end
29
+
30
+ def filepath
31
+ "#{params[:id]}.#{params[:format]}".gsub(%r{^/}, '')
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,4 @@
1
+ module AmcmsFilemanager
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module AmcmsFilemanager
2
+ module FilemanagerHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module AmcmsFilemanager
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Filemanager class for handling all things file related
4
+ class AmcmsFilemanager::Filemanager
5
+ class << self
6
+ # Make a copy of file in it's current directory
7
+ # @param [String | nil] dir
8
+ # @return [Sttring]
9
+ def copy(filename)
10
+ source_file = Rails.root.join('public', filename.gsub(/^\//, ''))
11
+
12
+ return false if File.directory?(source_file)
13
+
14
+ destination_dir = File.dirname(source_file)
15
+ base_name = File.basename(source_file)
16
+
17
+ destination_path = File.join(destination_dir, base_name)
18
+
19
+ if File.exist?(destination_path)
20
+ i = 1
21
+ while File.exist?(destination_path)
22
+ unique_name = "#{File.basename(source_file, '.*')}_#{i}#{File.extname(source_file)}"
23
+ destination_path = File.join(destination_dir, unique_name)
24
+ i += 1
25
+ end
26
+ end
27
+
28
+ FileUtils.cp(source_file, destination_path)
29
+ destination_path
30
+ end
31
+
32
+ def directory?(filename)
33
+ source_file = Rails.root.join('public', filename.gsub(/^\//, ''))
34
+
35
+ File.directory?(source_file)
36
+ end
37
+
38
+ # List all files in a given directory
39
+ # @param [String | nil] dir
40
+ # @param [String] type
41
+ # @return [Array]
42
+ def list(dir = nil, type = 'image')
43
+ dir = AmcmsFilemanager.config.root_path unless dir.present?
44
+ file_filter = AmcmsFilemanager.config.extension_allowlist.present? ? "*.{#{AmcmsFilemanager.config.extension_allowlist[type.to_sym].join(',')}}" : '*'
45
+ entries = Dir.glob(Rails.root.join('public', dir.gsub(%r{^/}, ''), file_filter))
46
+ entries.map! { |entry| file_details(entry) }
47
+
48
+ folders = entries.select { |entry| entry if entry[:directory] }.sort_by { |entry| entry[:filename] }.compact
49
+ files = entries.reject { |entry| entry if entry[:directory] }.sort_by { |entry| entry[:filename] }.compact
50
+
51
+ folders + files
52
+ end
53
+
54
+ # Return the parent directory
55
+ # @param [String | nil] dir
56
+ # @return [String]
57
+ def parent_dir(dir = nil)
58
+ dir = AmcmsFilemanager.config.root_path if !dir.present? || dir.blank?
59
+ return dir if dir == AmcmsFilemanager.config.root_path
60
+
61
+ File.dirname(dir).gsub(%r{^/}, '')
62
+ end
63
+
64
+ # Make a new directory
65
+ # @param [String] dir
66
+ # @param [String] directory_name
67
+ # @return [Integer]
68
+ def mkdir(dir, directory_name)
69
+ dir = AmcmsFilemanager.config.root_path unless dir.present?
70
+ Dir.mkdir(File.join(Rails.root.join('public', dir.gsub(%r{^/}, '')), directory_name), 0o775)
71
+ end
72
+
73
+ # Check to see if the directory exists
74
+ # @param [String] dir
75
+ # @param [String] filename
76
+ # @return [TrueClass, FalseClass]
77
+ def exists?(dir, filename)
78
+ dir = AmcmsFilemanager.config.root_path unless dir.present?
79
+ File.exist?(File.join(Rails.root.join('public', dir.gsub(%r{^/}, '')), filename))
80
+ end
81
+
82
+ # Delete a file or folder
83
+ # @param [String] file
84
+ # @return [Integer]
85
+ def delete(file)
86
+ filepath = Rails.root.join('public', file.gsub(%r{^/}, ''))
87
+
88
+ return File.delete(filepath) unless File.directory?(filepath)
89
+
90
+ FileUtils.remove_dir(filepath)
91
+ end
92
+
93
+ # Renames a file or directory
94
+ # @param [String] dir
95
+ # @param [String] original_filename
96
+ # @param [String] new_filename
97
+ # @return [Integer]
98
+ def rename(dir, original_filename, new_filename)
99
+ original_filepath = filepath(dir, original_filename)
100
+ new_filepath = filepath(dir, new_filename)
101
+
102
+ File.rename(original_filepath, new_filepath)
103
+ end
104
+
105
+ # Upload a file to the given directory
106
+ # @param [String] dir
107
+ # @param [ActionDispatch::Http::UploadedFile] file
108
+ # @param [String] type
109
+ def upload(dir, file, type = 'image')
110
+ uploader = AmcmsFilemanager::FilemanagerUploader.new
111
+ uploader.define_singleton_method(:store_dir) do
112
+ AmcmsFilemanager::Filemanager.filepath(dir, '')
113
+ end
114
+ uploader.store!(file)
115
+ end
116
+
117
+ # Create the filepath
118
+ # @param [String] dir
119
+ # @param [String] filename
120
+ # @return [String]
121
+ def filepath(dir, filename)
122
+ dir = "#{AmcmsFilemanager.config.root_path}/#{dir}" unless dir.include? AmcmsFilemanager.config.root_path
123
+ Rails.root.join('public', dir.gsub(%r{^/}, ''), filename)
124
+ end
125
+
126
+ # Detect if the file is an image
127
+ # @param [File] file
128
+ # @return [TrueClass, FalseClass]
129
+ def image?(file)
130
+ return false if File.directory?(file)
131
+
132
+ web_safe_images = [
133
+ 'image/apng',
134
+ 'image/avif',
135
+ 'image/gif',
136
+ 'image/jpeg',
137
+ 'image/jpg',
138
+ 'image/png',
139
+ 'image/webp'
140
+ ].freeze
141
+
142
+ web_safe_images.include?(mimetype(file))
143
+ end
144
+
145
+ private
146
+
147
+ # Present a hash of the file attributes
148
+ # @param [String] file
149
+ # @return [Hash{Symbol->TrueClass | FalseClass}]
150
+ def file_details(file)
151
+ {
152
+ filename: File.basename(file),
153
+ filepath: file.gsub(Rails.root.join('public').to_s, ''),
154
+ directory: File.directory?(file),
155
+ size: filesize(file),
156
+ mimetype: mimetype(file),
157
+ image: image?(file),
158
+ date_modified: File.mtime(file).strftime('%d/%m/%Y %H:%M')
159
+ }
160
+ end
161
+
162
+ # Get the mimetype of the file
163
+ # @param [File] file
164
+ # @return [String]
165
+ def mimetype(file)
166
+ return '' if File.directory?(file)
167
+
168
+ Marcel::MimeType.for Pathname.new(file)
169
+ end
170
+
171
+ # Calculate the size of the file, returning the units of measure with the value
172
+ # @param [String] file
173
+ # @return [String (frozen)]
174
+ def filesize(file)
175
+ size = File.size(file) / 1000
176
+ return "#{size} kb" if size < 1000
177
+
178
+ "#{size / 1000} mb"
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AmcmsFilemanager::Imagemanager
4
+
5
+ end
@@ -0,0 +1,6 @@
1
+ module AmcmsFilemanager
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module AmcmsFilemanager
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,56 @@
1
+ require 'carrierwave'
2
+
3
+ class AmcmsFilemanager::FilemanagerUploader < CarrierWave::Uploader::Base
4
+ # Include RMagick or MiniMagick support:
5
+ # include CarrierWave::RMagick
6
+ include CarrierWave::MiniMagick
7
+
8
+ def initialize(type = 'image')
9
+ @file_type = type
10
+ super
11
+ end
12
+
13
+ # Choose what kind of storage to use for this uploader:
14
+ storage :file
15
+ # storage :fog
16
+
17
+ # Override the directory where uploaded files will be stored.
18
+ # This is a sensible default for uploaders that are meant to be mounted:
19
+ def store_dir
20
+
21
+ end
22
+
23
+ # Provide a default URL as a default if there hasn't been a file uploaded:
24
+ # def default_url(*args)
25
+ # # For Rails 3.1+ asset pipeline compatibility:
26
+ # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
27
+ #
28
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
29
+ # end
30
+
31
+ # Process files as they are uploaded:
32
+ # process scale: [200, 300]
33
+ #
34
+ # def scale(width, height)
35
+ # # do something
36
+ # end
37
+
38
+ # Create different versions of your uploaded files:
39
+ # version :thumb do
40
+ # process resize_to_fit: [50, 50]
41
+ # end
42
+
43
+ # Add an allowlist of extensions which are allowed to be uploaded.
44
+ # For images you might use something like this:
45
+ def extension_allowlist
46
+ return %w[jpg jpeg gif] unless AmcmsFilemanager.config.try(:extension_allowlist).present?
47
+
48
+ AmcmsFilemanager.config.extension_allowlist[@file_type.to_sym]
49
+ end
50
+
51
+ # Override the filename of the uploaded files:
52
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
53
+ # def filename
54
+ # "something.jpg" if original_filename
55
+ # end
56
+ end