summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/asset.rb24
-rw-r--r--app/models/asset_license.rb15
-rw-r--r--app/models/concerns/file_attachment.rb52
-rw-r--r--app/models/node.rb2
-rw-r--r--app/models/node_action.rb8
-rw-r--r--app/models/page.rb26
-rw-r--r--app/models/related_asset.rb9
7 files changed, 106 insertions, 30 deletions
diff --git a/app/models/asset.rb b/app/models/asset.rb
index aca0ee8..ba9c2f0 100644
--- a/app/models/asset.rb
+++ b/app/models/asset.rb
@@ -1,12 +1,34 @@
1class Asset < ApplicationRecord 1class Asset < ApplicationRecord
2 IMAGE_CONTENT_TYPES = ["image/gif", "image/jpeg", "image/png", "image/webp"]
3 PDF_CONTENT_TYPE = "application/pdf"
2 4
3 include FileAttachment 5 include FileAttachment
4 6
5 has_many :related_assets, :dependent => :destroy 7 has_many :related_assets, :dependent => :destroy
6 has_many :pages, :through => :related_assets 8 has_many :pages, :through => :related_assets
7 9
8 scope :images, -> { where(:upload_content_type => ["image/gif", "image/jpeg", "image/png", "image/webp"]) } 10 scope :images, -> { where(:upload_content_type => IMAGE_CONTENT_TYPES) }
9 scope :documents, -> { where(:upload_content_type => ["application/pdf", "text/plain", "text/rtf"]) } 11 scope :documents, -> { where(:upload_content_type => ["application/pdf", "text/plain", "text/rtf"]) }
10 scope :audio, -> { where(:upload_content_type => ["audio/mpeg", "audio/x-m4a", "audio/wav", "audio/x-wav"]) } 12 scope :audio, -> { where(:upload_content_type => ["audio/mpeg", "audio/x-m4a", "audio/wav", "audio/x-wav"]) }
13 scope :pdfs, -> { where(:upload_content_type => PDF_CONTENT_TYPE) }
11 14
15 scope :headline_eligible, -> { where(:upload_content_type => IMAGE_CONTENT_TYPES + [PDF_CONTENT_TYPE]) }
16
17 validates :license_key, inclusion: { in: -> { AssetLicense.keys } }, allow_blank: true
18
19 def image?
20 IMAGE_CONTENT_TYPES.include?(upload_content_type)
21 end
22
23 def pdf?
24 upload_content_type == PDF_CONTENT_TYPE
25 end
26
27 def has_credit?
28 creator.present? || source_url.present? || license_key.present?
29 end
30
31 def show_credit?
32 image? && has_credit?
33 end
12end 34end
diff --git a/app/models/asset_license.rb b/app/models/asset_license.rb
new file mode 100644
index 0000000..ca66d40
--- /dev/null
+++ b/app/models/asset_license.rb
@@ -0,0 +1,15 @@
1class AssetLicense
2 Entry = Struct.new(:key, :url, :requires_attribution, :style, keyword_init: true)
3
4 DICTIONARY = YAML.load_file(Rails.root.join("config", "asset_licenses.yml")).freeze
5
6 def self.keys
7 DICTIONARY.keys
8 end
9
10 def self.find(key)
11 entry = DICTIONARY[key.to_s] if key.present?
12 return nil unless entry
13 Entry.new(key: key.to_s, url: entry["url"], requires_attribution: entry["requires_attribution"], style: entry["style"])
14 end
15end
diff --git a/app/models/concerns/file_attachment.rb b/app/models/concerns/file_attachment.rb
index 3c09456..6221312 100644
--- a/app/models/concerns/file_attachment.rb
+++ b/app/models/concerns/file_attachment.rb
@@ -31,6 +31,7 @@ module FileAttachment
31 31
32 IMAGE_CONTENT_TYPES = %w[image/jpeg image/gif image/png image/webp].freeze 32 IMAGE_CONTENT_TYPES = %w[image/jpeg image/gif image/png image/webp].freeze
33 VECTOR_CONTENT_TYPES = %w[image/svg+xml].freeze 33 VECTOR_CONTENT_TYPES = %w[image/svg+xml].freeze
34 RASTERIZED_CONTENT_TYPES = %w[application/pdf].freeze
34 DISPLAYABLE_AS_IMAGE = IMAGE_CONTENT_TYPES + VECTOR_CONTENT_TYPES 35 DISPLAYABLE_AS_IMAGE = IMAGE_CONTENT_TYPES + VECTOR_CONTENT_TYPES
35 36
36 included do 37 included do
@@ -52,29 +53,49 @@ module FileAttachment
52 build_upload_proxy 53 build_upload_proxy
53 end 54 end
54 55
56 def has_variant?(style)
57 upload_file_name.present? && File.exist?(file_path(style))
58 end
59
60 def previewable?
61 has_variant?(:medium)
62 end
63
64 def variant_filename(style)
65 return upload_file_name if style == :original || !RASTERIZED_CONTENT_TYPES.include?(upload_content_type)
66 File.basename(upload_file_name, ".*") + ".png"
67 end
68
55 private 69 private
56 70
57 def build_upload_proxy 71 def build_upload_proxy
58 @upload = UploadProxy.new(self) 72 @upload = UploadProxy.new(self)
59 end 73 end
60 74
75 class_methods do
76 def upload_root
77 Rails.env.test? ? Rails.root.join("tmp", "test_uploads") : Rails.root.join("public", "system", "uploads")
78 end
79 end
80
81 def upload_root
82 self.class.upload_root
83 end
84
61 def process_upload 85 def process_upload
62 return unless @pending_upload 86 return unless @pending_upload
63 uploaded_file = @pending_upload 87 uploaded_file = @pending_upload
64 @pending_upload = nil 88 @pending_upload = nil
65 89
66 old_dir = Rails.root.join("public", "system", "uploads", id.to_s) 90 old_dir = upload_root.join(id.to_s)
91
67 FileUtils.rm_rf(old_dir) if Dir.exist?(old_dir) 92 FileUtils.rm_rf(old_dir) if Dir.exist?(old_dir)
68 93
69 original_path = file_path(:original) 94 original_path = file_path(:original)
70 FileUtils.mkdir_p(File.dirname(original_path)) 95 FileUtils.mkdir_p(File.dirname(original_path))
71 FileUtils.cp(uploaded_file.tempfile.path, original_path) 96 FileUtils.cp(uploaded_file.tempfile.path, original_path)
72 97
73 if IMAGE_CONTENT_TYPES.include?(upload_content_type) 98 generate_all_variants(original_path)
74 generate_variants(original_path)
75 elsif VECTOR_CONTENT_TYPES.include?(upload_content_type)
76 generate_svg_variants(original_path)
77 end
78 end 99 end
79 100
80 def generate_variants(original_path) 101 def generate_variants(original_path)
@@ -93,16 +114,23 @@ module FileAttachment
93 end 114 end
94 end 115 end
95 116
117 def generate_all_variants(original_path)
118 if IMAGE_CONTENT_TYPES.include?(upload_content_type)
119 generate_variants(original_path)
120 elsif VECTOR_CONTENT_TYPES.include?(upload_content_type)
121 generate_svg_variants(original_path)
122 elsif RASTERIZED_CONTENT_TYPES.include?(upload_content_type)
123 generate_variants("#{original_path}[0]")
124 end
125 end
126
96 def delete_upload_files 127 def delete_upload_files
97 dir = Rails.root.join("public", "system", "uploads", id.to_s) 128 dir = upload_root.join(id.to_s)
98 FileUtils.rm_rf(dir) if Dir.exist?(dir) 129 FileUtils.rm_rf(dir) if Dir.exist?(dir)
99 end 130 end
100 131
101 def file_path(style) 132 def file_path(style)
102 Rails.root.join( 133 upload_root.join(id.to_s, style.to_s, variant_filename(style)).to_s
103 "public", "system", "uploads",
104 id.to_s, style.to_s, upload_file_name
105 ).to_s
106 end 134 end
107 135
108 def sanitize_filename(filename) 136 def sanitize_filename(filename)
@@ -119,7 +147,7 @@ module FileAttachment
119 147
120 def url(style = :original) 148 def url(style = :original)
121 return "" if @record.upload_file_name.blank? 149 return "" if @record.upload_file_name.blank?
122 "/system/uploads/#{@record.id}/#{style}/#{@record.upload_file_name}" 150 "/system/uploads/#{@record.id}/#{style}/#{@record.variant_filename(style)}"
123 end 151 end
124 152
125 def content_type 153 def content_type
diff --git a/app/models/node.rb b/app/models/node.rb
index a440c2f..0796ea4 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -443,7 +443,7 @@ class Node < ApplicationRecord
443 end 443 end
444 444
445 def title 445 def title
446 head ? head.title : draft.title 446 editable_page&.title
447 end 447 end
448 448
449 def update_unique_names? 449 def update_unique_names?
diff --git a/app/models/node_action.rb b/app/models/node_action.rb
index 13bd5ba..63a99ae 100644
--- a/app/models/node_action.rb
+++ b/app/models/node_action.rb
@@ -166,4 +166,12 @@ class NodeAction < ApplicationRecord
166 def subject_name 166 def subject_name
167 metadata["human_readable_node_name"] || node&.unique_name || "deleted node" 167 metadata["human_readable_node_name"] || node&.unique_name || "deleted node"
168 end 168 end
169
170 def diff_link_params
171 prev = NodeAction.where(node_id: node_id, action: "publish")
172 .where("id < ?", id)
173 .order(id: :desc).first
174 return nil unless prev&.page && page
175 { start_revision: prev.page.revision, end_revision: page.revision }
176 end
169end 177end
diff --git a/app/models/page.rb b/app/models/page.rb
index f33d88d..817e3bd 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -220,7 +220,12 @@ class Page < ApplicationRecord
220 end 220 end
221 221
222 # Clone asset references 222 # Clone asset references
223 self.assets = page.assets 223 self.related_assets.delete_all
224 page.related_assets.each do |related|
225 self.related_assets.create!(:asset_id => related.asset_id,
226 :position => related.position,
227 :headline => related.headline)
228 end
224 229
225 self.save 230 self.save
226 end 231 end
@@ -287,6 +292,10 @@ class Page < ApplicationRecord
287 end 292 end
288 end 293 end
289 294
295 def headline_asset
296 related_assets.find_by(headline: true)&.asset
297 end
298
290 # Returns true if a page has translations where one of them is significantly 299 # Returns true if a page has translations where one of them is significantly
291 # older than the other. 300 # older than the other.
292 # Takes the I18n.default locale and a second :locale to test if the 301 # Takes the I18n.default locale and a second :locale to test if the
@@ -314,21 +323,6 @@ class Page < ApplicationRecord
314 end 323 end
315 end 324 end
316 325
317 def update_assets image_ids
318
319 transaction do
320 self.related_assets.delete_all
321
322 if image_ids
323 image_ids.each_with_index do |id, index|
324 asset = Asset.find(id)
325 self.related_assets.create!(:asset_id => asset.id, :position => index+1)
326 end
327 end
328 end
329
330 end
331
332 # Installs (or re-installs) the trigger that keeps page_translations' 326 # Installs (or re-installs) the trigger that keeps page_translations'
333 # search_vector in sync. Idempotent, safe to call on every boot. 327 # search_vector in sync. Idempotent, safe to call on every boot.
334 # search_vector is populated by a raw Postgres trigger, not anything 328 # search_vector is populated by a raw Postgres trigger, not anything
diff --git a/app/models/related_asset.rb b/app/models/related_asset.rb
index 8f16460..8f8d49c 100644
--- a/app/models/related_asset.rb
+++ b/app/models/related_asset.rb
@@ -5,4 +5,13 @@ class RelatedAsset < ApplicationRecord
5 acts_as_list :scope => :page_id 5 acts_as_list :scope => :page_id
6 6
7 default_scope -> { order("position ASC") } 7 default_scope -> { order("position ASC") }
8
9 validate :headline_only_for_images
10
11 private
12
13 def headline_only_for_images
14 return unless asset
15 errors.add(:headline, "can only be set on image or PDF assets") if headline? && !(asset.image? || asset.pdf?)
16 end
8end 17end