udongo 5.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/backend/application.js +1 -1
  3. data/app/controllers/backend/assets_controller.rb +52 -0
  4. data/app/models/asset.rb +37 -0
  5. data/app/models/asset_image.rb +172 -0
  6. data/app/models/concerns/imageable.rb +13 -0
  7. data/app/models/image.rb +11 -0
  8. data/app/uploaders/asset_uploader.rb +36 -0
  9. data/app/views/backend/assets/_filter.html.erb +17 -0
  10. data/app/views/backend/assets/_form.html.erb +28 -0
  11. data/app/views/backend/assets/edit.html.erb +5 -0
  12. data/app/views/backend/assets/index.html.erb +48 -0
  13. data/app/views/backend/assets/new.html.erb +4 -0
  14. data/app/views/backend/snippets/edit.html.erb +1 -1
  15. data/app/views/backend/users/index.html.erb +2 -2
  16. data/app/views/layouts/backend/_top_navigation.html.erb +1 -0
  17. data/changelog.md +9 -0
  18. data/config/locales/en.yml +2 -1
  19. data/config/locales/en_backend.yml +6 -0
  20. data/config/locales/en_general.yml +1 -0
  21. data/config/locales/fr.yml +1 -1
  22. data/config/locales/nl.yml +2 -1
  23. data/config/locales/nl_backend.yml +5 -0
  24. data/config/locales/nl_general.yml +1 -0
  25. data/config/routes.rb +2 -0
  26. data/db/migrate/20170225144523_create_assets.rb +14 -0
  27. data/db/migrate/20170228183831_create_images.rb +18 -0
  28. data/lib/udongo/configs/assets.rb +10 -0
  29. data/lib/udongo/version.rb +1 -1
  30. data/readme.md +11 -0
  31. data/spec/factories/addresses.rb +6 -0
  32. data/spec/factories/admins.rb +10 -0
  33. data/spec/factories/assets.rb +5 -0
  34. data/spec/factories/comments.rb +9 -0
  35. data/spec/factories/content_columns.rb +10 -0
  36. data/spec/factories/content_images.rb +9 -0
  37. data/spec/factories/content_rows.rb +5 -0
  38. data/spec/factories/content_texts.rb +4 -0
  39. data/spec/factories/email_templates.rb +8 -0
  40. data/spec/factories/emails.rb +11 -0
  41. data/spec/factories/images.rb +5 -0
  42. data/spec/factories/logs.rb +4 -0
  43. data/spec/factories/meta.rb +7 -0
  44. data/spec/factories/navigation_items.rb +5 -0
  45. data/spec/factories/navigations.rb +6 -0
  46. data/spec/factories/notes.rb +6 -0
  47. data/spec/factories/pages.rb +5 -0
  48. data/spec/factories/queued_tasks.rb +5 -0
  49. data/spec/factories/redirects.rb +7 -0
  50. data/spec/factories/search_indices.rb +6 -0
  51. data/spec/factories/search_modules.rb +5 -0
  52. data/spec/factories/search_synonyms.rb +7 -0
  53. data/spec/factories/settings.rb +7 -0
  54. data/spec/factories/snippets.rb +6 -0
  55. data/spec/factories/stores.rb +6 -0
  56. data/spec/factories/tagged_items.rb +5 -0
  57. data/spec/factories/tags.rb +7 -0
  58. data/spec/factories/users.rb +9 -0
  59. data/spec/support/concerns/cacheable.rb +10 -0
  60. data/spec/support/concerns/commentable.rb +10 -0
  61. data/spec/support/concerns/content_type.rb +20 -0
  62. data/spec/support/concerns/deletable.rb +33 -0
  63. data/spec/support/concerns/draggable.rb +29 -0
  64. data/spec/support/concerns/emailable.rb +7 -0
  65. data/spec/support/concerns/flexible_content.rb +22 -0
  66. data/spec/support/concerns/imageable.rb +15 -0
  67. data/spec/support/concerns/locale.rb +21 -0
  68. data/spec/support/concerns/loggable.rb +19 -0
  69. data/spec/support/concerns/notable.rb +10 -0
  70. data/spec/support/concerns/parentable.rb +73 -0
  71. data/spec/support/concerns/person.rb +15 -0
  72. data/spec/support/concerns/publishable.rb +41 -0
  73. data/spec/support/concerns/searchable.rb +130 -0
  74. data/spec/support/concerns/seo.rb +54 -0
  75. data/spec/support/concerns/sortable.rb +67 -0
  76. data/spec/support/concerns/spammable.rb +53 -0
  77. data/spec/support/concerns/storable.rb +18 -0
  78. data/spec/support/concerns/taggable.rb +51 -0
  79. data/spec/support/concerns/translatable.rb +71 -0
  80. data/spec/support/concerns/visible.rb +64 -0
  81. metadata +66 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6e7758e4a1bfb4e24e8b4ff54ba00225802be713
4
- data.tar.gz: 2c45f1de07eac96c9127c5bfb0c338bec8754f51
3
+ metadata.gz: e6a22b011bc3569827aedbcdd99d353e735fae6e
4
+ data.tar.gz: 3202da4e9d86005256c04c4826bc9b0b3eaede7b
5
5
  SHA512:
6
- metadata.gz: 3f2a7cbd3e48e5a773ed2bfe24ccbcc0eeb10ae6570b32bea19381875e30ebb710bbd4761cd6063aeee7ff41912dc52923a057ea6986f33ae48dff0771985d59
7
- data.tar.gz: 7291bf66476a79eba40459c33e5234ed20679f9c37177d0f1e6ed22ad061edce684c3730b7f2a396f7008037b676a0af08d616394023edf69fd0729d65487781
6
+ metadata.gz: 0a90d20c8888527092dea454829c2f04cf507f7af964d1d747b3fa10e4473b5dd1753d2051295d7a6ed727d868f5f72a1ce8571f16425b3f1ae1731aa5728792
7
+ data.tar.gz: ce3492185bef64ce7b2ccc6786a41089560ac1555a0814ff7ae26c5ba4e92941d15b7f278639652c9b3a9fcc187677d3224a06796c3b8b9a5ec02a7ced5bb802
@@ -10,7 +10,7 @@
10
10
  // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
11
  // about supported directives.
12
12
  //
13
- //= require jquery
13
+ //= require jquery3
14
14
  //= require jquery_ujs
15
15
  //= require jquery-ui/autocomplete
16
16
  //= require jquery-ui.autocomplete.html.js
@@ -0,0 +1,52 @@
1
+ class Backend::AssetsController < Backend::BaseController
2
+ include Concerns::PaginationController
3
+
4
+ before_action :find_model, only: [:show, :edit, :update, :destroy]
5
+ before_action -> { breadcrumb.add t('b.files'), backend_assets_path }
6
+
7
+ def index
8
+ @search = Asset.ransack params[:q]
9
+ @assets = @search.result(distinct: true).order('id DESC').page(page_number).per_page(per_page)
10
+ end
11
+
12
+ def show
13
+ redirect_to @model.filename.url
14
+ end
15
+
16
+ def new
17
+ @model = Asset.new
18
+ end
19
+
20
+ def create
21
+ @model = Asset.new allowed_params
22
+
23
+ if @model.save
24
+ redirect_to backend_assets_path, notice: translate_notice(:added, :file)
25
+ else
26
+ render :new
27
+ end
28
+ end
29
+
30
+ def update
31
+ if @model.update_attributes allowed_params
32
+ redirect_to backend_assets_path, notice: translate_notice(:edited, :file)
33
+ else
34
+ render :edit
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ @model.destroy if @model.deletable?
40
+ redirect_to backend_assets_path, notice: translate_notice(:deleted, :file)
41
+ end
42
+
43
+ private
44
+
45
+ def find_model
46
+ @model = Asset.find params[:id]
47
+ end
48
+
49
+ def allowed_params
50
+ params[:asset].permit(:filename, :description)
51
+ end
52
+ end
@@ -0,0 +1,37 @@
1
+ class Asset < ApplicationRecord
2
+ include Concerns::Taggable
3
+
4
+ mount_uploader :filename, AssetUploader
5
+
6
+ has_many :images, dependent: :destroy
7
+
8
+ before_save :update_filesize, :update_content_type
9
+
10
+ validates :filename, presence: true
11
+
12
+ def image?
13
+ content_type.to_s.include?('image')
14
+ end
15
+
16
+ def image
17
+ AssetImage.new(self) if image?
18
+ end
19
+
20
+ def actual_filename
21
+ read_attribute(:filename)
22
+ end
23
+
24
+ def deletable?
25
+ images.empty?
26
+ end
27
+
28
+ private
29
+
30
+ def update_filesize
31
+ self.filesize = filename.size
32
+ end
33
+
34
+ def update_content_type
35
+ self.content_type = filename.content_type
36
+ end
37
+ end
@@ -0,0 +1,172 @@
1
+ require 'mini_magick'
2
+ require 'fileutils'
3
+
4
+ class AssetImage
5
+ def initialize(asset)
6
+ @asset = asset
7
+ end
8
+
9
+ def filename(width = nil, height = nil, options = {})
10
+ options[:action] = :resize_to_limit unless options.key?(:action)
11
+
12
+ str = options[:action].to_s.split('_').last
13
+
14
+ if options[:quality]
15
+ str << '-q' + options[:quality].to_s
16
+ end
17
+
18
+ if options[:gravity]
19
+ str << '-g' + options[:gravity].to_s.underscore.split('_').map { |s| s[0,1] }.join
20
+ end
21
+
22
+ if options[:background]
23
+ str << '-b' + options[:background].to_s.parameterize
24
+ end
25
+
26
+ str << "-#{width}x#{height}-#{@asset.actual_filename}"
27
+ end
28
+
29
+ def url(width = nil, height = nil, options = {})
30
+ options[:action] = :resize_to_limit unless options.key?(:action)
31
+
32
+ if width.nil? && height.nil?
33
+ return "/uploads/assets/#{main_dir}/#{second_dir}/#{@asset.actual_filename}"
34
+ end
35
+
36
+ name = filename(width, height, options)
37
+
38
+ unless File.exists?(actual_path(name))
39
+ FileUtils.mkpath(File.dirname(actual_path(name)))
40
+
41
+ case options[:action].to_sym
42
+ when :resize_to_limit then resize_to_limit(width, height, options)
43
+ when :resize_to_fit then resize_to_fit(width, height, options)
44
+ when :resize_to_fill then resize_to_fill(width, height, options)
45
+ when :resize_and_pad then resize_and_pad(width, height, options)
46
+ else
47
+ raise "No such resize action '#{options[:action].to_s}'. Available are: resize_to_limit, resize_to_fit, resize_to_fill and resize_and_pad."
48
+ end
49
+ end
50
+
51
+ actual_url(name)
52
+ end
53
+
54
+ def actual_url(calculated_filename)
55
+ "/uploads/assets/_cache/#{main_dir}/#{second_dir}/#{calculated_filename}"
56
+ end
57
+
58
+ def path(width = nil, height = nil, options = {})
59
+ url(width, height, options) # Trigger the actual resize
60
+ actual_path(filename(width, height, options))
61
+ end
62
+
63
+ def actual_path(calculated_filename)
64
+ "#{Rails.root}/public/uploads/assets/_cache/#{main_dir}/#{second_dir}/#{calculated_filename}"
65
+ end
66
+
67
+ private
68
+
69
+ # Resize the image to fit within the specified dimensions while retaining
70
+ # the original aspect ratio. Will only resize the image if it is larger than the
71
+ # specified dimensions. The resulting image may be shorter or narrower than specified
72
+ # in the smaller dimension but will not be larger than the specified values.
73
+ #
74
+ def resize_to_limit(width, height, options = {})
75
+ img = MiniMagick::Image.open(@asset.filename.path)
76
+ img.combine_options do |c|
77
+ c.quality options[:quality] if options[:quality]
78
+ c.resize "#{width}x#{height}>"
79
+ end
80
+
81
+ img.write(actual_path(filename(width, height, options)))
82
+ end
83
+
84
+ # Resize the image to fit within the specified dimensions while retaining
85
+ # the original aspect ratio. The image may be shorter or narrower than
86
+ # specified in the smaller dimension but will not be larger than the specified values.
87
+ #
88
+ def resize_to_fit(width, height, options = {})
89
+ img = MiniMagick::Image.open(@asset.filename.path)
90
+ img.combine_options do |c|
91
+ c.quality options[:quality] if options[:quality]
92
+ c.resize "#{width}x#{height}"
93
+ end
94
+
95
+ img.write(actual_path(filename(width, height, options)))
96
+ end
97
+
98
+ # Resize the image to fit within the specified dimensions while retaining
99
+ # the aspect ratio of the original image. If necessary, crop the image in the
100
+ # larger dimension.
101
+ #
102
+ # Possible values for options[:gravity] are:
103
+ # NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast
104
+ #
105
+ def resize_to_fill(width, height, options = {})
106
+ gravity = options.key?(:gravity) ? options[:gravity] : 'Center'
107
+
108
+ img = MiniMagick::Image.open(@asset.filename.path)
109
+ cols, rows = img[:dimensions]
110
+
111
+ img.combine_options do |cmd|
112
+ if width != cols || height != rows
113
+ scale_x = width/cols.to_f
114
+ scale_y = height/rows.to_f
115
+
116
+ if scale_x >= scale_y
117
+ cols = (scale_x * (cols + 0.5)).round
118
+ rows = (scale_x * (rows + 0.5)).round
119
+ cmd.resize "#{cols}"
120
+ else
121
+ cols = (scale_y * (cols + 0.5)).round
122
+ rows = (scale_y * (rows + 0.5)).round
123
+ cmd.resize "x#{rows}"
124
+ end
125
+ end
126
+
127
+ cmd.quality options[:quality] if options.key?(:quality)
128
+ cmd.gravity gravity
129
+ cmd.background 'rgba(255,255,255,0.0)'
130
+ cmd.extent "#{width}x#{height}" if cols != width || rows != height
131
+ end
132
+
133
+ img.write(actual_path(filename(width, height, options)))
134
+ end
135
+
136
+ # Resize the image to fit within the specified dimensions while retaining
137
+ # the original aspect ratio. If necessary, will pad the remaining area
138
+ # with the given color, which defaults to transparent (for gif and png,
139
+ # white for jpeg).
140
+ #
141
+ # Possible values for options[:gravity] are:
142
+ # NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast
143
+ #
144
+ def resize_and_pad(width, height, options = {})
145
+ gravity = options.key?(:gravity) ? options[:gravity] : 'Center'
146
+ background = options.key?(:background) ? options[:background] : :transparant
147
+
148
+ img = MiniMagick::Image.open(@asset.filename.path)
149
+ img.combine_options do |cmd|
150
+ cmd.thumbnail "#{width}x#{height}>"
151
+
152
+ if background.to_sym == :transparent
153
+ cmd.background 'rgba(255, 255, 255, 0.0)'
154
+ else
155
+ cmd.background background
156
+ end
157
+
158
+ cmd.gravity gravity
159
+ cmd.extent "#{width}x#{height}"
160
+ end
161
+
162
+ img.write(actual_path(filename(width, height, options)))
163
+ end
164
+
165
+ def main_dir
166
+ @main_dir ||= Digest::MD5.hexdigest(@asset.id.to_s)[0,2]
167
+ end
168
+
169
+ def second_dir
170
+ @second_dir ||= Digest::MD5.hexdigest(@asset.id.to_s)[2,2]
171
+ end
172
+ end
@@ -0,0 +1,13 @@
1
+ module Concerns
2
+ module Imageable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :images, as: :imageable
7
+ end
8
+
9
+ def imageable?
10
+ true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ class Image < ApplicationRecord
2
+ include Concerns::Visible
3
+
4
+ include Concerns::Sortable
5
+ sortable scope: [:asset_id]
6
+
7
+ belongs_to :asset
8
+ belongs_to :imageable, polymorphic: true
9
+
10
+ validates :asset, presence: true
11
+ end
@@ -0,0 +1,36 @@
1
+ class AssetUploader < CarrierWave::Uploader::Base
2
+ include CarrierWave::RMagick
3
+
4
+ storage :file
5
+
6
+ def filename
7
+ @name ||= "#{secure_token}.#{file.extension.downcase}" if original_filename
8
+ end
9
+
10
+ private
11
+
12
+ def extension_white_list
13
+ if model.image?
14
+ Udongo.config.assets.image_white_list
15
+ else
16
+ (Udongo.config.assets.image_white_list + Udongo.config.assets.file_white_list).flatten
17
+ end
18
+ end
19
+
20
+ def secure_token
21
+ ivar = "@#{mounted_as}_secure_token"
22
+ token = model.instance_variable_get(ivar)
23
+ token ||= model.instance_variable_set(ivar, SecureRandom.hex(4))
24
+ end
25
+
26
+ def store_dir
27
+ md5 = Digest::MD5.hexdigest(model.id.to_s)
28
+ "uploads/assets/#{md5[0,2]}/#{md5[2,2]}"
29
+ end
30
+
31
+ def image?(new_file)
32
+ new_file.content_type.include? 'image'
33
+ end
34
+
35
+ process resize_to_limit: [2560, 2560], if: :image?
36
+ end
@@ -0,0 +1,17 @@
1
+ <div class="card filter">
2
+ <div class="card-block">
3
+ <%= search_form_for [:backend, @search] do |f| %>
4
+ <div class="row">
5
+ <div class="col-md-6 form-group">
6
+ <%= f.search_field :description_cont, placeholder: t('b.description'), class: 'form-control' %>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="row">
11
+ <div class="col-md-6">
12
+ <%= f.submit class: 'btn btn-primary btn-sm' %>
13
+ </div>
14
+ </div>
15
+ <% end %>
16
+ </div>
17
+ </div>
@@ -0,0 +1,28 @@
1
+ <%= render 'backend/general_form_error', object: @model %>
2
+
3
+ <%= simple_form_for [:backend, @model] do |f| %>
4
+ <div class="row">
5
+ <div class="col-md-6">
6
+ <div class="card">
7
+ <div class="card-header">
8
+ <%= t 'b.general' %>
9
+ </div>
10
+
11
+ <div class="card-block">
12
+ <%= f.input :filename, as: :file, label: false, required: @model.new_record? %>
13
+ <%= f.input :description, as: :string %>
14
+ </div>
15
+ </div>
16
+ </div>
17
+
18
+ <% unless @model.new_record? %>
19
+ <div class="col-md-6">
20
+ <!-- Tags -->
21
+ <%= render 'backend/tags', model: @model, locale: Udongo.config.i18n.app.default_locale %>
22
+ </div>
23
+ <% end %>
24
+ </div>
25
+
26
+
27
+ <%= render 'backend/form_actions', cancel_url: backend_assets_path %>
28
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <% breadcrumb.add truncate(@model.description, length: 20), edit_backend_snippet_path(@model) %>
2
+ <% breadcrumb.add t('b.edit') %>
3
+ <%= render 'backend/breadcrumbs' %>
4
+
5
+ <%= render 'form' %>
@@ -0,0 +1,48 @@
1
+ <%= render 'backend/breadcrumbs' %>
2
+ <%= render 'filter' %>
3
+
4
+ <p class="text-xs-right">
5
+ <%= link_to icon(:plus, t('b.add')), new_backend_asset_path, class: 'btn btn-primary btn-sm' %>
6
+ </p>
7
+
8
+ <% if @assets.any? %>
9
+ <table class="table table-striped table-hover">
10
+ <thead class="thead-inverse">
11
+ <tr>
12
+ <th><%= t 'b.file' %></th>
13
+ <th><%= t 'g.type' %></th>
14
+ <th><%= t 'b.size' %></th>
15
+ <th><%= t 'b.description' %></th>
16
+ <th><%= t 'b.last_changed_at' %></th>
17
+ <th>&nbsp;</th>
18
+ </tr>
19
+ </thead>
20
+
21
+ <tbody>
22
+ <% @assets.each do |a| %>
23
+ <tr>
24
+ <td>
25
+ <% if a.image? %>
26
+ <%= image_tag a.image.url(150, 150), alt: '', class: 'img-fluid' %>
27
+ <% else %>
28
+ <%= link_to icon(:download, t('b.download')), a.filename.url %>
29
+ <% end %>
30
+ </td>
31
+ <td><%= a.actual_filename.split('.').last.upcase %></td>
32
+ <td><%= number_to_human_size a.filesize %></td>
33
+ <td><%= simple_format a.description %></td>
34
+ <td><%= l a.updated_at %></td>
35
+ <td class="text-xs-right">
36
+ <%= link_to_edit [:backend, a] %>
37
+ <%= link_to_delete [:backend, a] if a.deletable? %>
38
+ </td>
39
+ </tr>
40
+ <% end %>
41
+ </tbody>
42
+ </table>
43
+
44
+ <%= udongo_paginate @assets %>
45
+
46
+ <% else %>
47
+ <p><%= t 'b.msg.no_items' %></p>
48
+ <% end %>