udongo 5.1.0 → 5.2.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.
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 %>