diff options
Diffstat (limited to 'app/models')
| -rw-r--r-- | app/models/asset.rb | 24 | ||||
| -rw-r--r-- | app/models/asset_license.rb | 15 | ||||
| -rw-r--r-- | app/models/concerns/file_attachment.rb | 52 | ||||
| -rw-r--r-- | app/models/node.rb | 2 | ||||
| -rw-r--r-- | app/models/node_action.rb | 8 | ||||
| -rw-r--r-- | app/models/page.rb | 26 | ||||
| -rw-r--r-- | app/models/related_asset.rb | 9 |
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 @@ | |||
| 1 | class Asset < ApplicationRecord | 1 | class 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 | ||
| 12 | end | 34 | end |
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 @@ | |||
| 1 | class 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 | ||
| 15 | end | ||
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 | ||
| 169 | end | 177 | end |
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 | ||
| 8 | end | 17 | end |
