rails_admin_settings 1.4.0 → 1.6.0
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rakeTasks +7 -0
- data/.travis.yml +15 -4
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +156 -116
- data/README.md +16 -1
- data/app/models/rails_admin_settings/setting.rb +0 -1
- data/app/views/rails_admin/main/_setting_value.html.haml +1 -2
- data/lib/generators/rails_admin_settings/templates/migration.rb +3 -0
- data/lib/rails_admin_settings.rb +75 -42
- data/lib/rails_admin_settings/determine_mime_type.rb +193 -0
- data/lib/rails_admin_settings/kinds.rb +1 -0
- data/lib/rails_admin_settings/processing.rb +14 -5
- data/lib/rails_admin_settings/rails_admin_config.rb +2 -3
- data/lib/rails_admin_settings/settings.rb +3 -26
- data/lib/rails_admin_settings/storage/shrine_uploader.rb +14 -0
- data/lib/rails_admin_settings/uploads.rb +12 -1
- data/lib/rails_admin_settings/validation.rb +89 -57
- data/lib/rails_admin_settings/version.rb +1 -1
- data/rails_admin_settings.gemspec +6 -2
- data/spec/factories/setting.rb +1 -1
- data/spec/shrine_spec.rb +34 -0
- data/spec/spec_helper.rb +16 -2
- data/spec/support/1024x768.gif +0 -0
- data/spec/types_spec.rb +7 -0
- metadata +66 -8
- data/.ruby-gemset +0 -2
- data/.ruby-version +0 -1
@@ -1,4 +1,4 @@
|
|
1
|
-
- if ['string', 'integer', 'phone', 'email', 'address', 'url', 'domain'].include?(@object.type)
|
1
|
+
- if ['string', 'integer', 'float', 'phone', 'email', 'address', 'url', 'domain'].include?(@object.type)
|
2
2
|
= form.text_field :raw, :value => field.value
|
3
3
|
- if @object.type == 'boolean'
|
4
4
|
= form.check_box :raw, {:value => field.value}, 'true', 'false'
|
@@ -8,7 +8,6 @@
|
|
8
8
|
= form.text_area :raw, :value => field.value, :rows => 10, :cols => 80
|
9
9
|
- elsif @object.type == 'code' || @object.type == 'sanitize_code'
|
10
10
|
:ruby
|
11
|
-
#binding.pry
|
12
11
|
js_data = {
|
13
12
|
csspath: asset_path('ckeditor/plugins/codemirror/css/codemirror.min.css'),
|
14
13
|
jspath: asset_path('ckeditor/plugins/codemirror/js/codemirror.min.js'),
|
data/lib/rails_admin_settings.rb
CHANGED
@@ -6,6 +6,20 @@ module RailsAdminSettings
|
|
6
6
|
end
|
7
7
|
cattr_accessor :scrubber
|
8
8
|
|
9
|
+
class PersistenceException < Exception
|
10
|
+
end
|
11
|
+
|
12
|
+
autoload :Mongoid, "rails_admin_settings/mongoid"
|
13
|
+
autoload :Fallback, "rails_admin_settings/fallback"
|
14
|
+
autoload :Namespaced, "rails_admin_settings/namespaced"
|
15
|
+
autoload :Processing, "rails_admin_settings/processing"
|
16
|
+
autoload :Validation, "rails_admin_settings/validation"
|
17
|
+
autoload :RequireHelpers, "rails_admin_settings/require_helpers"
|
18
|
+
autoload :RailsAdminConfig, "rails_admin_settings/rails_admin_config"
|
19
|
+
autoload :Uploads, "rails_admin_settings/uploads"
|
20
|
+
autoload :HexColorValidator, "rails_admin_settings/hex_color_validator"
|
21
|
+
autoload :Dumper, "rails_admin_settings/dumper"
|
22
|
+
|
9
23
|
class << self
|
10
24
|
def orm
|
11
25
|
if defined?(::Mongoid)
|
@@ -22,55 +36,74 @@ module RailsAdminSettings
|
|
22
36
|
def active_record?
|
23
37
|
orm == :active_record
|
24
38
|
end
|
25
|
-
end
|
26
|
-
|
27
|
-
class PersistenceException < Exception
|
28
|
-
end
|
29
|
-
|
30
|
-
autoload :Mongoid, "rails_admin_settings/mongoid"
|
31
|
-
autoload :Fallback, "rails_admin_settings/fallback"
|
32
|
-
autoload :Namespaced, "rails_admin_settings/namespaced"
|
33
|
-
autoload :Processing, "rails_admin_settings/processing"
|
34
|
-
autoload :Validation, "rails_admin_settings/validation"
|
35
|
-
autoload :RequireHelpers, "rails_admin_settings/require_helpers"
|
36
|
-
autoload :RailsAdminConfig, "rails_admin_settings/rails_admin_config"
|
37
|
-
autoload :Uploads, "rails_admin_settings/uploads"
|
38
|
-
autoload :HexColorValidator, "rails_admin_settings/hex_color_validator"
|
39
|
-
autoload :Dumper, "rails_admin_settings/dumper"
|
40
39
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
40
|
+
def apply_defaults!(file, verbose = false)
|
41
|
+
if File.file?(file)
|
42
|
+
puts "[settings] Loading from #{file}" if verbose
|
43
|
+
if defined?(Psych) && Psych.respond_to?(:safe_load)
|
44
|
+
yaml = Psych.safe_load(File.read(file))
|
45
|
+
else
|
46
|
+
yaml = YAML.load(File.read(file), safe: true)
|
47
|
+
end
|
48
|
+
yaml.each_pair do |namespace, vals|
|
49
|
+
process_defaults(namespace, vals, verbose)
|
50
|
+
end
|
52
51
|
end
|
53
52
|
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.track_history!
|
57
|
-
return false unless Settings.table_exists?
|
58
53
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
54
|
+
def process_defaults(namespace, vals, verbose = false)
|
55
|
+
vals.symbolize_keys!
|
56
|
+
n = Settings.ns(namespace)
|
57
|
+
vals.each_pair do |key, val|
|
58
|
+
val.symbolize_keys!
|
59
|
+
if !val[:kind].nil? && (val[:kind] == 'file' || val[:kind] == 'image')
|
60
|
+
unless Settings.file_uploads_supported
|
61
|
+
raise PersistenceException, "Fatal: setting #{key} is #{val[:type]} but file upload engine is not detected"
|
62
|
+
end
|
63
|
+
value = File.open(Settings.root_file_path.join(val.delete(:value)))
|
64
|
+
else
|
65
|
+
value = val.delete(:value)
|
66
|
+
end
|
67
|
+
puts "#{key} - default '#{value}' current '#{Settings.get(key).raw}'" if verbose
|
68
|
+
n.set(key, value, val.merge(overwrite: false))
|
65
69
|
end
|
66
|
-
|
67
|
-
|
70
|
+
n.unload!
|
71
|
+
end
|
72
|
+
|
73
|
+
def migrate!
|
74
|
+
if RailsAdminSettings.mongoid?
|
75
|
+
RailsAdminSettings::Setting.where(:ns.exists => false).update_all(ns: 'main')
|
76
|
+
RailsAdminSettings::Setting.all.each do |s|
|
77
|
+
s.kind = s.read_attribute(:type) if !s.read_attribute(:type).blank? && s.kind != s.read_attribute(:type)
|
78
|
+
s.save! if s.changed?
|
79
|
+
s.unset(:type)
|
80
|
+
end
|
68
81
|
else
|
69
|
-
|
82
|
+
if Settings.table_exists?
|
83
|
+
RailsAdminSettings::Setting.where("ns IS NULL").update_all(ns: 'main')
|
84
|
+
end
|
70
85
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
86
|
+
end
|
87
|
+
|
88
|
+
def track_history!
|
89
|
+
return false unless Settings.table_exists?
|
90
|
+
|
91
|
+
if mongoid?
|
92
|
+
if ::Mongoid.const_defined?('History')
|
93
|
+
RailsAdminSettings::Setting.send(:include, ::Mongoid::History::Trackable)
|
94
|
+
RailsAdminSettings::Setting.send(:track_history, {track_create: true, track_destroy: true})
|
95
|
+
else
|
96
|
+
puts "[rails_admin_settings] WARN unable to track_history: Mongoid::History not loaded!"
|
97
|
+
end
|
98
|
+
if ::Mongoid.const_defined?('Userstamp')
|
99
|
+
RailsAdminSettings::Setting.send(:include, ::Mongoid::Userstamp)
|
100
|
+
else
|
101
|
+
puts "[rails_admin_settings] WARN unable to track_history: Mongoid::Userstamp not loaded!"
|
102
|
+
end
|
103
|
+
elsif active_record?
|
104
|
+
if defined?(PaperTrail) && PaperTrail::Version.table_exists?
|
105
|
+
RailsAdminSettings::Setting.send(:has_paper_trail)
|
106
|
+
end
|
74
107
|
end
|
75
108
|
end
|
76
109
|
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Shrine
|
4
|
+
module Plugins
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/determine_mime_type
|
6
|
+
module DetermineMimeType
|
7
|
+
LOG_SUBSCRIBER = -> (event) do
|
8
|
+
Shrine.logger.info "MIME Type (#{event.duration}ms) – #{{
|
9
|
+
io: event[:io].class,
|
10
|
+
uploader: event[:uploader],
|
11
|
+
}.inspect}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
15
|
+
uploader.opts[:determine_mime_type] ||= { analyzer: :file, analyzer_options: {} }
|
16
|
+
uploader.opts[:determine_mime_type].merge!(opts)
|
17
|
+
|
18
|
+
# instrumentation plugin integration
|
19
|
+
uploader.subscribe(:mime_type, &log_subscriber) if uploader.respond_to?(:subscribe)
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# Determines the MIME type of the IO object by calling the specified
|
24
|
+
# analyzer.
|
25
|
+
def determine_mime_type(io)
|
26
|
+
analyzer = opts[:determine_mime_type][:analyzer]
|
27
|
+
|
28
|
+
analyzer = mime_type_analyzer(analyzer) if analyzer.is_a?(Symbol)
|
29
|
+
args = if analyzer.is_a?(Proc)
|
30
|
+
[io, mime_type_analyzers].take(analyzer.arity.abs)
|
31
|
+
else
|
32
|
+
[io, opts[:determine_mime_type][:analyzer_options]]
|
33
|
+
end
|
34
|
+
|
35
|
+
mime_type = instrument_mime_type(io) { analyzer.call(*args) }
|
36
|
+
io.rewind
|
37
|
+
|
38
|
+
mime_type
|
39
|
+
end
|
40
|
+
alias mime_type determine_mime_type
|
41
|
+
|
42
|
+
# Returns a hash of built-in MIME type analyzers, where keys are
|
43
|
+
# analyzer names and values are `#call`-able objects which accepts the
|
44
|
+
# IO object.
|
45
|
+
def mime_type_analyzers
|
46
|
+
@mime_type_analyzers ||= MimeTypeAnalyzer::SUPPORTED_TOOLS.inject({}) do |hash, tool|
|
47
|
+
hash.merge!(tool => mime_type_analyzer(tool))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns callable mime type analyzer object.
|
52
|
+
def mime_type_analyzer(name)
|
53
|
+
MimeTypeAnalyzer.new(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Sends a `mime_type.shrine` event for instrumentation plugin.
|
59
|
+
def instrument_mime_type(io, &block)
|
60
|
+
return yield unless respond_to?(:instrument)
|
61
|
+
|
62
|
+
instrument(:mime_type, io: io, &block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module InstanceMethods
|
67
|
+
private
|
68
|
+
|
69
|
+
# Calls the configured MIME type analyzer.
|
70
|
+
def extract_mime_type(io)
|
71
|
+
self.class.determine_mime_type(io)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class MimeTypeAnalyzer
|
76
|
+
SUPPORTED_TOOLS = [:fastimage, :file, :filemagic, :mimemagic, :marcel, :mime_types, :mini_mime, :content_type]
|
77
|
+
MAGIC_NUMBER = 256 * 1024
|
78
|
+
|
79
|
+
def initialize(tool)
|
80
|
+
raise Error, "unknown mime type analyzer #{tool.inspect}, supported analyzers are: #{SUPPORTED_TOOLS.join(",")}" unless SUPPORTED_TOOLS.include?(tool)
|
81
|
+
|
82
|
+
@tool = tool
|
83
|
+
end
|
84
|
+
|
85
|
+
def call(io, options = {})
|
86
|
+
mime_type = send(:"extract_with_#{@tool}", io, options)
|
87
|
+
io.rewind
|
88
|
+
|
89
|
+
mime_type
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def extract_with_file(io, options)
|
95
|
+
require "open3"
|
96
|
+
|
97
|
+
return nil if io.eof? # file command returns "application/x-empty" for empty files
|
98
|
+
|
99
|
+
Open3.popen3(*%W[file --mime-type --brief -]) do |stdin, stdout, stderr, thread|
|
100
|
+
begin
|
101
|
+
IO.copy_stream(io, stdin.binmode)
|
102
|
+
rescue Errno::EPIPE
|
103
|
+
end
|
104
|
+
stdin.close
|
105
|
+
|
106
|
+
status = thread.value
|
107
|
+
|
108
|
+
raise Error, "file command failed to spawn: #{stderr.read}" if status.nil?
|
109
|
+
raise Error, "file command failed: #{stderr.read}" unless status.success?
|
110
|
+
|
111
|
+
$stderr.print(stderr.read)
|
112
|
+
|
113
|
+
output = stdout.read.strip
|
114
|
+
|
115
|
+
raise Error, "file command failed: #{output}" if output.include?("cannot open")
|
116
|
+
|
117
|
+
output
|
118
|
+
end
|
119
|
+
rescue Errno::ENOENT
|
120
|
+
raise Error, "file command-line tool is not installed"
|
121
|
+
end
|
122
|
+
|
123
|
+
def extract_with_fastimage(io, options)
|
124
|
+
require "fastimage"
|
125
|
+
|
126
|
+
type = FastImage.type(io)
|
127
|
+
"image/#{type}" if type
|
128
|
+
end
|
129
|
+
|
130
|
+
def extract_with_filemagic(io, options)
|
131
|
+
require "filemagic"
|
132
|
+
|
133
|
+
return nil if io.eof? # FileMagic returns "application/x-empty" for empty files
|
134
|
+
|
135
|
+
FileMagic.open(FileMagic::MAGIC_MIME_TYPE) do |filemagic|
|
136
|
+
filemagic.buffer(io.read(MAGIC_NUMBER))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def extract_with_mimemagic(io, options)
|
141
|
+
require "mimemagic"
|
142
|
+
|
143
|
+
mime = MimeMagic.by_magic(io)
|
144
|
+
mime&.type
|
145
|
+
end
|
146
|
+
|
147
|
+
def extract_with_marcel(io, options)
|
148
|
+
require "marcel"
|
149
|
+
|
150
|
+
return nil if io.eof? # marcel returns "application/octet-stream" for empty files
|
151
|
+
|
152
|
+
filename = (options[:filename_fallback] ? extract_filename(io) : nil)
|
153
|
+
Marcel::MimeType.for(io, name: filename)
|
154
|
+
end
|
155
|
+
|
156
|
+
def extract_with_mime_types(io, options)
|
157
|
+
require "mime/types"
|
158
|
+
|
159
|
+
if filename = extract_filename(io)
|
160
|
+
mime_type = MIME::Types.of(filename).first
|
161
|
+
mime_type&.content_type
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def extract_with_mini_mime(io, options)
|
166
|
+
require "mini_mime"
|
167
|
+
|
168
|
+
if filename = extract_filename(io)
|
169
|
+
info = MiniMime.lookup_by_filename(filename)
|
170
|
+
info&.content_type
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def extract_with_content_type(io, options)
|
175
|
+
if io.respond_to?(:content_type) && io.content_type
|
176
|
+
io.content_type.split(";").first
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def extract_filename(io)
|
181
|
+
if io.respond_to?(:original_filename)
|
182
|
+
io.original_filename
|
183
|
+
elsif io.respond_to?(:path)
|
184
|
+
File.basename(io.path)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
register_plugin(:determine_mime_type, DetermineMimeType)
|
192
|
+
end
|
193
|
+
end
|
@@ -10,7 +10,7 @@ module RailsAdminSettings
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def text_kind?
|
13
|
-
(RailsAdminSettings.kinds - ['phone', 'phones', 'integer', 'yaml', 'json', 'boolean']).include? kind
|
13
|
+
(RailsAdminSettings.kinds - ['phone', 'phones', 'integer', 'float', 'yaml', 'json', 'boolean']).include? kind
|
14
14
|
end
|
15
15
|
|
16
16
|
def upload_kind?
|
@@ -31,10 +31,14 @@ module RailsAdminSettings
|
|
31
31
|
|
32
32
|
def value
|
33
33
|
if upload_kind?
|
34
|
-
|
35
|
-
file
|
34
|
+
unless defined?(Shrine)
|
35
|
+
if file?
|
36
|
+
file.url
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
36
40
|
else
|
37
|
-
|
41
|
+
file.url if file.present?
|
38
42
|
end
|
39
43
|
elsif raw.blank? || disabled?
|
40
44
|
default_value
|
@@ -54,7 +58,8 @@ module RailsAdminSettings
|
|
54
58
|
end
|
55
59
|
|
56
60
|
def to_s
|
57
|
-
if yaml_kind? || json_kind? || phone_kind? || integer_kind?
|
61
|
+
if yaml_kind? || json_kind? || phone_kind? || integer_kind? || float_kind?
|
62
|
+
|
58
63
|
raw
|
59
64
|
else
|
60
65
|
value
|
@@ -94,6 +99,8 @@ module RailsAdminSettings
|
|
94
99
|
''
|
95
100
|
elsif integer_kind?
|
96
101
|
0
|
102
|
+
elsif float_kind?
|
103
|
+
0
|
97
104
|
elsif yaml_kind?
|
98
105
|
nil
|
99
106
|
elsif json_kind?
|
@@ -181,6 +188,8 @@ module RailsAdminSettings
|
|
181
188
|
process_text
|
182
189
|
elsif integer_kind?
|
183
190
|
raw.to_i
|
191
|
+
elsif float_kind?
|
192
|
+
raw.to_f
|
184
193
|
elsif yaml_kind?
|
185
194
|
load_yaml
|
186
195
|
elsif json_kind?
|
@@ -4,7 +4,6 @@ module RailsAdminSettings
|
|
4
4
|
if base.respond_to?(:rails_admin)
|
5
5
|
base.rails_admin do
|
6
6
|
navigation_label I18n.t('admin.settings.label')
|
7
|
-
|
8
7
|
list do
|
9
8
|
if Object.const_defined?('RailsAdminToggleable')
|
10
9
|
field :enabled, :toggle
|
@@ -16,9 +15,9 @@ module RailsAdminSettings
|
|
16
15
|
field :name
|
17
16
|
field :raw do
|
18
17
|
pretty_value do
|
19
|
-
if bindings[:object].file_kind?
|
18
|
+
if bindings[:object].file_kind? and !defined?(Shrine) and bindings[:object].to_path.present?
|
20
19
|
"<a href='#{CGI::escapeHTML(bindings[:object].file.url)}'>#{CGI::escapeHTML(bindings[:object].to_path)}</a>".html_safe
|
21
|
-
elsif bindings[:object].image_kind?
|
20
|
+
elsif bindings[:object].image_kind? and !defined?(Shrine) and !bindings[:object].file.nil?
|
22
21
|
"<a href='#{CGI::escapeHTML(bindings[:object].file.url)}'><img src='#{CGI::escapeHTML(bindings[:object].file.url)}' /></a>".html_safe
|
23
22
|
else
|
24
23
|
value
|
@@ -39,6 +39,8 @@ class Settings < BasicObject
|
|
39
39
|
|
40
40
|
def table_exists?
|
41
41
|
RailsAdminSettings.mongoid? || RailsAdminSettings::Setting.table_exists?
|
42
|
+
rescue ActiveRecord::NoDatabaseError
|
43
|
+
false
|
42
44
|
end
|
43
45
|
|
44
46
|
def unload!
|
@@ -64,32 +66,7 @@ class Settings < BasicObject
|
|
64
66
|
end
|
65
67
|
|
66
68
|
def apply_defaults!(file, verbose = false)
|
67
|
-
|
68
|
-
puts "[settings] Loading from #{file}" if verbose
|
69
|
-
if defined?(Psych) && Psych.respond_to?(:safe_load)
|
70
|
-
yaml = Psych.safe_load(File.read(file))
|
71
|
-
else
|
72
|
-
yaml = YAML.load(File.read(file), safe: true)
|
73
|
-
end
|
74
|
-
yaml.each_pair do |namespace, vals|
|
75
|
-
vals.symbolize_keys!
|
76
|
-
n = ns(namespace)
|
77
|
-
vals.each_pair do |key, val|
|
78
|
-
val.symbolize_keys!
|
79
|
-
if !val[:kind].nil? && (val[:kind] == 'file' || val[:kind] == 'image')
|
80
|
-
unless @@file_uploads_supported
|
81
|
-
::Kernel.raise ::RailsAdminSettings::PersistenceException, "Fatal: setting #{key} is #{val[:type]} but file upload engine is not detected"
|
82
|
-
end
|
83
|
-
value = File.open(root_file_path.join(val.delete(:value)))
|
84
|
-
else
|
85
|
-
value = val.delete(:value)
|
86
|
-
end
|
87
|
-
puts "#{key} - default '#{value}' current '#{Settings.get(key).raw}'" if verbose
|
88
|
-
n.set(key, value, val.merge(overwrite: false))
|
89
|
-
end
|
90
|
-
n.unload!
|
91
|
-
end
|
92
|
-
end
|
69
|
+
RailsAdminSettings.apply_defaults!(file, verbose)
|
93
70
|
end
|
94
71
|
|
95
72
|
def get(key, options = {})
|