Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix editor image routing #12683

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
Expand Up @@ -9,7 +9,7 @@ class ResultForm < Decidim::Form
include TranslationsHelper

translatable_attribute :title, String
translatable_attribute :description, String
translatable_attribute :description, Decidim::Attributes::RichText

attribute :decidim_scope_id, Integer
attribute :decidim_category_id, Integer
Expand Down
Expand Up @@ -11,7 +11,7 @@ class TimelineEntryForm < Decidim::Form
attribute :decidim_accountability_result_id, Integer
attribute :entry_date, Decidim::Attributes::LocalizedDate
translatable_attribute :title, String
translatable_attribute :description, String
translatable_attribute :description, Decidim::Attributes::RichText

validates :entry_date, presence: true
validates :title, translatable_presence: true
Expand Down
2 changes: 1 addition & 1 deletion decidim-admin/app/forms/decidim/admin/help_section_form.rb
Expand Up @@ -9,7 +9,7 @@ class HelpSectionForm < Decidim::Form
include TranslationsHelper

attribute :id, String
translatable_attribute :content, String
translatable_attribute :content, Decidim::Attributes::RichText

def name
multi_translation("activerecord.models.#{manifest.model_class_name.underscore}.other")
Expand Down
Expand Up @@ -35,9 +35,9 @@ class OrganizationAppearanceForm < Form
attribute :alert_color, String

translatable_attribute :cta_button_text, String
translatable_attribute :description, String
translatable_attribute :description, Decidim::Attributes::RichText
translatable_attribute :highlighted_content_banner_title, String
translatable_attribute :highlighted_content_banner_short_description, String
translatable_attribute :highlighted_content_banner_short_description, Decidim::Attributes::RichText
translatable_attribute :highlighted_content_banner_action_title, String
translatable_attribute :highlighted_content_banner_action_subtitle, String
translatable_attribute :omnipresent_banner_title, String
Expand Down
4 changes: 2 additions & 2 deletions decidim-admin/app/forms/decidim/admin/organization_form.rb
Expand Up @@ -31,9 +31,9 @@ class OrganizationForm < Form
attribute :customize_welcome_notification, Boolean

translatable_attribute :welcome_notification_subject, String
translatable_attribute :welcome_notification_body, String
translatable_attribute :welcome_notification_body, Decidim::Attributes::RichText

translatable_attribute :admin_terms_of_service_body, String
translatable_attribute :admin_terms_of_service_body, Decidim::Attributes::RichText

validates :welcome_notification_subject, :welcome_notification_body, translatable_presence: true, if: proc { |form| form.customize_welcome_notification }

Expand Down
2 changes: 1 addition & 1 deletion decidim-admin/app/forms/decidim/admin/static_page_form.rb
Expand Up @@ -9,7 +9,7 @@ class StaticPageForm < Form
attribute :slug, String
validates :title, translatable_presence: true
translatable_attribute :title, String
translatable_attribute :content, String
translatable_attribute :content, Decidim::Attributes::RichText
attribute :changed_notably, Boolean
attribute :show_in_footer, Boolean
attribute :allow_public_access, Boolean
Expand Down
Expand Up @@ -14,23 +14,23 @@ class AssemblyForm < Form

mimic :assembly

translatable_attribute :composition, String
translatable_attribute :closing_date_reason, String
translatable_attribute :composition, Decidim::Attributes::RichText
translatable_attribute :closing_date_reason, Decidim::Attributes::RichText
translatable_attribute :created_by_other, String
translatable_attribute :description, String
translatable_attribute :description, Decidim::Attributes::RichText
translatable_attribute :developer_group, String
translatable_attribute :internal_organisation, String
translatable_attribute :internal_organisation, Decidim::Attributes::RichText
translatable_attribute :local_area, String
translatable_attribute :meta_scope, String
translatable_attribute :participatory_scope, String
translatable_attribute :participatory_structure, String
translatable_attribute :purpose_of_action, String
translatable_attribute :short_description, String
translatable_attribute :special_features, String
translatable_attribute :purpose_of_action, Decidim::Attributes::RichText
translatable_attribute :short_description, Decidim::Attributes::RichText
translatable_attribute :special_features, Decidim::Attributes::RichText
translatable_attribute :subtitle, String
translatable_attribute :target, String
translatable_attribute :title, String
translatable_attribute :announcement, String
translatable_attribute :announcement, Decidim::Attributes::RichText

attribute :created_by, String
attribute :facebook_handler, String
Expand Down
2 changes: 1 addition & 1 deletion decidim-blogs/app/forms/decidim/blogs/admin/post_form.rb
Expand Up @@ -8,7 +8,7 @@ class PostForm < Decidim::Form
include TranslatableAttributes

translatable_attribute :title, String
translatable_attribute :body, String
translatable_attribute :body, Decidim::Attributes::RichText

attribute :decidim_author_id, Integer
attribute :published_at, Decidim::Attributes::TimeWithZone
Expand Down
Expand Up @@ -11,7 +11,7 @@ class BudgetForm < Decidim::Form

translatable_attribute :title, String
attribute :weight, Integer, default: 0
translatable_attribute :description, String
translatable_attribute :description, Decidim::Attributes::RichText
attribute :total_budget, Integer, default: 0
attribute :decidim_scope_id, Integer

Expand Down
Expand Up @@ -11,7 +11,7 @@ class ProjectForm < Decidim::Form
include Decidim::ApplicationHelper

translatable_attribute :title, String
translatable_attribute :description, String
translatable_attribute :description, Decidim::Attributes::RichText

attribute :address, String
attribute :latitude, Float
Expand Down
Expand Up @@ -12,10 +12,10 @@ class ConferenceForm < Form

translatable_attribute :title, String
translatable_attribute :slogan, String
translatable_attribute :short_description, String
translatable_attribute :description, String
translatable_attribute :objectives, String
translatable_attribute :registration_terms, String
translatable_attribute :short_description, Decidim::Attributes::RichText
translatable_attribute :description, Decidim::Attributes::RichText
translatable_attribute :objectives, Decidim::Attributes::RichText
translatable_attribute :registration_terms, Decidim::Attributes::RichText

mimic :conference

Expand Down
Expand Up @@ -10,7 +10,7 @@ class ConferenceSpeakerForm < Form

translatable_attribute :position, String
translatable_attribute :affiliation, String
translatable_attribute :short_bio, String
translatable_attribute :short_bio, Decidim::Attributes::RichText

mimic :conference_speaker

Expand Down
Expand Up @@ -11,7 +11,7 @@ class RegistrationTypeForm < Form
mimic :conference_registration_type

translatable_attribute :title, String
translatable_attribute :description, String
translatable_attribute :description, Decidim::Attributes::RichText

attribute :weight, Integer
attribute :price, Decimal
Expand Down
11 changes: 10 additions & 1 deletion decidim-core/app/helpers/decidim/sanitize_helper.rb
Expand Up @@ -37,13 +37,22 @@ def decidim_sanitize_newsletter(html, options = {})
end
end

# Converts the blob and blob variant references to blob URLs.
def decidim_rich_text(html, **)
renderer = Decidim::ContentProcessor.renderer_klass(:blob).constantize.new(html)
renderer.render(**)
end

def decidim_sanitize_editor(html, options = {})
content_tag(:div, decidim_sanitize(html, options), class: %w(rich-text-display))
end

def decidim_sanitize_editor_admin(html, options = {})
html = Decidim::IframeDisabler.new(html, options).perform
decidim_sanitize_editor(html, { scrubber: Decidim::AdminInputScrubber.new }.merge(options))
decidim_sanitize_editor(
decidim_rich_text(html),
{ scrubber: Decidim::AdminInputScrubber.new }.merge(options)
)
end

def decidim_html_escape(text)
Expand Down
2 changes: 2 additions & 0 deletions decidim-core/lib/decidim/attributes.rb
Expand Up @@ -5,6 +5,7 @@ module Attributes
autoload :TimeWithZone, "decidim/attributes/time_with_zone"
autoload :LocalizedDate, "decidim/attributes/localized_date"
autoload :CleanString, "decidim/attributes/clean_string"
autoload :RichText, "decidim/attributes/rich_text"
autoload :Blob, "decidim/attributes/blob"
autoload :Array, "decidim/attributes/array"
autoload :Hash, "decidim/attributes/hash"
Expand All @@ -27,6 +28,7 @@ module Attributes
ActiveModel::Type.register(:"decidim/attributes/time_with_zone", Decidim::Attributes::TimeWithZone)
ActiveModel::Type.register(:"decidim/attributes/localized_date", Decidim::Attributes::LocalizedDate)
ActiveModel::Type.register(:"decidim/attributes/clean_string", Decidim::Attributes::CleanString)
ActiveModel::Type.register(:"decidim/attributes/rich_text", Decidim::Attributes::RichText)
ActiveModel::Type.register(:"decidim/attributes/blob", Decidim::Attributes::Blob)

# Overrides
Expand Down
38 changes: 38 additions & 0 deletions decidim-core/lib/decidim/attributes/rich_text.rb
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module Decidim
module Attributes
# Custom attributes value to convert rich text strings in the database, i.e.
# strings that originate from the editor.
class RichText < Decidim::Attributes::CleanString
def type
:"decidim/attributes/rich_text"
end

# Serializes the value to the database.
def serialize(value)
serialize_value(value)
end

private

# From form to database
def serialize_value(value)
return value unless value.is_a?(String)

context = {}
parsed = Decidim::ContentProcessor.parse_with_processor(:blob, value, context)
parsed.rewrite
end

# From database to form
def cast_value(value)
clean_string = super
return clean_string unless clean_string.is_a?(String)

renderer = Decidim::ContentProcessor.renderer_klass(:blob).constantize.new(clean_string)
renderer.render
end
end
end
end
1 change: 1 addition & 0 deletions decidim-core/lib/decidim/content_parsers.rb
Expand Up @@ -3,6 +3,7 @@
module Decidim
module ContentParsers
autoload :BaseParser, "decidim/content_parsers/base_parser"
autoload :BlobParser, "decidim/content_parsers/blob_parser"
autoload :UserParser, "decidim/content_parsers/user_parser"
autoload :UserGroupParser, "decidim/content_parsers/user_group_parser"
autoload :HashtagParser, "decidim/content_parsers/hashtag_parser"
Expand Down
93 changes: 93 additions & 0 deletions decidim-core/lib/decidim/content_parsers/blob_parser.rb
@@ -0,0 +1,93 @@
# frozen_string_literal: true

module Decidim
module ContentParsers
# Parses any blob URLs from the content and replaces them with references
# to those blobs.
class BlobParser < BaseParser
# Matches all possible URLs pointing to ActiveStorage::Blob objects.
#
# Possible routes:
# get "/blobs/redirect/:signed_id/*filename"
# get "/blobs/proxy/:signed_id/*filename"
# get "/blobs/:signed_id/*filename"
# get "/representations/redirect/:signed_blob_id/:variation_key/*filename"
# get "/representations/proxy/:signed_blob_id/:variation_key/*filename"
# get "/representations/:signed_blob_id/:variation_key/*filename"
# get "/disk/:encoded_key/*filename"
#
# See:
# https://github.com/rails/rails/blob/a7e379896552ce43b822385c03c37f2bd47739d3/activestorage/config/routes.rb#L5-L14
BLOB_REGEX = %r{
# Group 1: Host part
(
# Group 2: Domain and subpath part
https?://((?!/rails).)+
)?
/rails/active_storage
# Group 3: Blob path, representation path or disk service path
/(blobs/redirect|blobs/proxy|blobs|representations/redirect|representations/proxy|representations|disk)
# Group 4: Signed ID for blobs or encoded key for disk service
/([^/]+)
# Group 5: Variation part (only for representations)
(
# Group 6: Variation key for representations
/([\w.=-]+)
)?
# Group 7: Filename
/([\w.=-]+)
}x

def rewrite
replace_blobs(content)
end

private

def replace_blobs(text)
text.gsub(BLOB_REGEX) do |match|
type_part = Regexp.last_match(3)
key_part = Regexp.last_match(4)

variation_key = nil
blob =
if type_part == "disk"
# Disk service URL
decoded = ActiveStorage.verifier.verified(key_part, purpose: :blob_key)
ActiveStorage::Blob.find_by(key: decoded[:key]) if decoded
else
# Representation or blob
if type_part.start_with?("representations")
# Representation
variation_part = Regexp.last_match(6)
variation_key = generate_variation_key(variation_part)
end

ActiveStorage::Blob.find_signed(key_part)
end
next match unless blob

"#{blob.to_global_id}#{"/#{variation_key}" if variation_key}"
end
end

def generate_variation_key(variation_part)
# The variation part has to be decoded because it will eventually
# expire. This way we can preserve the variation information
# longer.
variation = ActiveStorage.verifier.verify(variation_part, purpose: :variation)
return unless variation

# Convert to base64 encoded JSON string for better representation within
# the URLs. This manually encoded part will not expire as it is
# persisted to the database.
Base64.strict_encode64(ActiveSupport::JSON.encode(variation))
rescue ActiveSupport::MessageVerifier::InvalidSignature
# This happens if the variation key is already expired in which
# case it cannot be represented and instead a URL to the blob is
# created.
variation_part
end
end
end
end
1 change: 1 addition & 0 deletions decidim-core/lib/decidim/content_renderers.rb
Expand Up @@ -3,6 +3,7 @@
module Decidim
module ContentRenderers
autoload :BaseRenderer, "decidim/content_renderers/base_renderer"
autoload :BlobRenderer, "decidim/content_renderers/blob_renderer"
autoload :UserRenderer, "decidim/content_renderers/user_renderer"
autoload :UserGroupRenderer, "decidim/content_renderers/user_group_renderer"
autoload :HashtagRenderer, "decidim/content_renderers/hashtag_renderer"
Expand Down