amcms_filemanager 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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