Skip to content

Commit

Permalink
Backport 'Fix for internal links not displaying on page title' to v0.…
Browse files Browse the repository at this point in the history
…26 (decidim#9407)

* Fix for internal links not displaying on page title (decidim#9228)

* Display proposal's/meeting's title as a href when referring to internal links in proposal's description meeting's body and comments.

* Fix typo from renderer spec
Fix rubocop issues
Refactoring

* Fixing double break line rendering issue on resource

* Running Linters

* Fix link display on "compare proposals" step.
Check organization host when parsing internal links.

* Rewrite the organization check

Co-authored-by: Alexandru-Emil Lupu <[email protected]>

* Fix rubocop offence

Co-authored-by: roxanaopr <[email protected]>
Co-authored-by: Alexandru-Emil Lupu <[email protected]>
  • Loading branch information
3 people authored Jun 10, 2022
1 parent b9783f9 commit a6bddc3
Show file tree
Hide file tree
Showing 29 changed files with 457 additions and 104 deletions.
2 changes: 1 addition & 1 deletion decidim-core/app/cells/decidim/card_m/show.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<div class="card__text">
<div class="card__text--paragraph">
<%= render :badge if has_badge? %>
<%= description %>
<%= Decidim::ContentProcessor.render(description, "div") %>
</div>
</div>

Expand Down
10 changes: 8 additions & 2 deletions decidim-core/app/helpers/decidim/sanitize_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,17 @@ def content_handle_locale(body, all_locales, extras, links, strip_tags)
end
end

# This method is currently being used only for Proposal and Meeting,
# It aims to load the presenter class, and perform some basic sanitization on the content
# This method should be used along side simple_format.
# @param resource [Object] Resource object
# @param method [Symbol] Method name
#
# @return ActiveSupport::SafeBuffer
def render_sanitized_content(resource, method)
content = present(resource).send(method, links: true, strip_tags: !safe_content?)
content = simple_format(content, {}, sanitize: false)

return content unless safe_content?
return decidim_sanitize(content, {}) unless safe_content?

decidim_sanitize_editor(content)
end
Expand Down
1 change: 1 addition & 0 deletions decidim-core/lib/decidim/content_parsers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ module ContentParsers
autoload :NewlineParser, "decidim/content_parsers/newline_parser"
autoload :LinkParser, "decidim/content_parsers/link_parser"
autoload :InlineImagesParser, "decidim/content_parsers/inline_images_parser"
autoload :ResourceParser, "decidim/content_parsers/resource_parser"
end
end
97 changes: 97 additions & 0 deletions decidim-core/lib/decidim/content_parsers/resource_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# frozen_string_literal: true

module Decidim
module ContentParsers
# A parser that searches mentions of Resources in content.
#
# @see BaseParser Examples of how to use a content parser
class ResourceParser < BaseParser
# Matches a URL
URL_REGEX_SCHEME = '(?:http(s)?:\/\/)'
URL_REGEX_CONTENT = '[\w.-]+[\w\-\._~:\/?#\[\]@!\$&\'\(\)\*\+,;=.]+'
URL_REGEX_END_CHAR = '[\d]'
# Matches a mentioned resource ID (~(d)+ expression)
ID_REGEX = /~(\d+)/.freeze

# Replaces found mentions matching an existing
# Resource with a global id for that Resource. Other mentions found that doesn't
# match an existing Resource are returned as they are.
#
# @return [String] the content with the valid mentions replaced by a global id.
def rewrite
rewrited_content = parse_for_urls(content)
parse_for_ids(rewrited_content)
end

private

def parse_for_urls(content)
content.gsub(url_regex) do |match|
resource = resource_from_url_match(match)
if resource
update_metadata(resource)
resource.to_global_id
else
match
end
end
end

def parse_for_ids(content)
content.gsub(ID_REGEX) do |match|
resource = resource_from_id_match(Regexp.last_match(1))
if resource
update_metadata(resource)
resource.to_global_id
else
match
end
end
end

def resource_from_url_match(match)
uri = URI.parse(match)
return if uri.path.blank?
return unless find_organization(uri.host)

resource_id = uri.path.split("/").last
find_resource_by_id(resource_id)
rescue URI::InvalidURIError
Rails.logger.error("#{e.message}=>#{e.backtrace}")
nil
end

def resource_from_id_match(match)
resource_id = match
find_resource_by_id(resource_id)
end

def find_resource_by_id(id)
if id.present?
spaces = Decidim.participatory_space_manifests.flat_map do |manifest|
manifest.participatory_spaces.call(context[:current_organization]).public_spaces
end
components = Component.where(participatory_space: spaces).published
model_class.constantize.where(component: components).find_by(id: id)
end
end

def find_organization(uri_host)
current_organization = context[:current_organization]
(current_organization.host == uri_host) || current_organization.secondary_hosts.include?(uri_host)
end

def url_regex
raise "Not implemented"
end

def model_class
raise "Not implemented"
end

def update_metadata(resource)
# code to update metadata - needs to be overwritten
end
end
end
end
3 changes: 2 additions & 1 deletion decidim-core/lib/decidim/content_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ def self.render(content, wrapper_tag = "p", options = {})
simple_format(
render_without_format(content, options),
{},
wrapper_tag: wrapper_tag
wrapper_tag: wrapper_tag,
sanitize: false
)
end

Expand Down
1 change: 1 addition & 0 deletions decidim-core/lib/decidim/content_renderers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ module ContentRenderers
autoload :UserGroupRenderer, "decidim/content_renderers/user_group_renderer"
autoload :HashtagRenderer, "decidim/content_renderers/hashtag_renderer"
autoload :LinkRenderer, "decidim/content_renderers/link_renderer"
autoload :ResourceRenderer, "decidim/content_renderers/resource_renderer"
end
end
30 changes: 30 additions & 0 deletions decidim-core/lib/decidim/content_renderers/resource_renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

module Decidim
module ContentRenderers
class ResourceRenderer < BaseRenderer
# Matches a global id representing a Decidim::User

# Replaces found Global IDs matching an existing resource with
# a link to its show page. The Global IDs representing an
# invalid Resource are replaced with '???' string.
#
# @return [String] the content ready to display (contains HTML)
def render(_options = nil)
return content unless content.respond_to?(:gsub)

content.gsub(regex) do |resource_gid|
resource = GlobalID::Locator.locate(resource_gid)
resource.presenter.display_mention
rescue ActiveRecord::RecordNotFound
resource_id = resource_gid.split("/").last
"~#{resource_id}"
end
end

def regex
raise "Not implemented"
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def call

def create_meeting!
parsed_title = Decidim::ContentProcessor.parse_with_processor(:hashtag, form.title, current_organization: form.current_organization).rewrite
parsed_description = Decidim::ContentProcessor.parse_with_processor(:hashtag, form.description, current_organization: form.current_organization).rewrite
parsed_description = Decidim::ContentProcessor.parse(form.description, current_organization: form.current_organization).rewrite
params = {
scope: form.scope,
category: form.category,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def call

def update_meeting!
parsed_title = Decidim::ContentProcessor.parse_with_processor(:hashtag, form.title, current_organization: form.current_organization).rewrite
parsed_description = Decidim::ContentProcessor.parse_with_processor(:hashtag, form.description, current_organization: form.current_organization).rewrite
parsed_description = Decidim::ContentProcessor.parse(form.description, current_organization: form.current_organization).rewrite

Decidim.traceability.update!(
meeting,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ def call

def create_meeting!
parsed_title = Decidim::ContentProcessor.parse_with_processor(:hashtag, form.title, current_organization: form.current_organization).rewrite
parsed_description = Decidim::ContentProcessor.parse_with_processor(:hashtag, form.description, current_organization: form.current_organization).rewrite
parsed_description = Decidim::ContentProcessor.parse(form.description, current_organization: form.current_organization).rewrite

params = {
scope: form.scope,
category: form.category,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def call

def update_meeting!
parsed_title = Decidim::ContentProcessor.parse_with_processor(:hashtag, form.title, current_organization: form.current_organization).rewrite
parsed_description = Decidim::ContentProcessor.parse_with_processor(:hashtag, form.description, current_organization: form.current_organization).rewrite
parsed_description = Decidim::ContentProcessor.parse(form.description, current_organization: form.current_organization).rewrite

Decidim.traceability.update!(
meeting,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def safe_content?

# If the content is safe, HTML tags are sanitized, otherwise, they are stripped.
def render_meeting_body(meeting)
render_sanitized_content(meeting, :description)
Decidim::ContentProcessor.render(render_sanitized_content(meeting, :description), "div")
end

def prevent_timeout_seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,21 @@ module Meetings
#
class MeetingPresenter < Decidim::ResourcePresenter
include Decidim::ResourceHelper
include ActionView::Helpers::UrlHelper
include Decidim::SanitizeHelper

def meeting
__getobj__
end

def meeting_path
Decidim::ResourceLocatorPresenter.new(meeting).path
end

def display_mention
link_to title, meeting_path
end

def title(links: false, html_escape: false, all_locales: false)
return unless meeting

Expand Down
20 changes: 20 additions & 0 deletions decidim-meetings/lib/decidim/content_parsers/meeting_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module Decidim
module ContentParsers
# A parser that searches mentions of Meetings in content.
#
# @see BaseParser Examples of how to use a content parser
class MeetingParser < ResourceParser
private

def url_regex
%r{#{URL_REGEX_SCHEME}#{URL_REGEX_CONTENT}/meetings/#{URL_REGEX_END_CHAR}+}i
end

def model_class
"Decidim::Meetings::Meeting"
end
end
end
end
17 changes: 17 additions & 0 deletions decidim-meetings/lib/decidim/content_renderers/meeting_renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module Decidim
module ContentRenderers
# A renderer that searches Global IDs representing meetings in content
# and replaces it with a link to their show page.
#
# e.g. gid://<APP_NAME>/Decidim::Meetings::Meeting/1
#
# @see BaseRenderer Examples of how to use a content renderer
class MeetingRenderer < ResourceRenderer
def regex
%r{gid://([\w-]*/Decidim::Meetings::Meeting/(\d+))}i
end
end
end
end
8 changes: 8 additions & 0 deletions decidim-meetings/lib/decidim/meetings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,12 @@ module Meetings
2.days
end
end

module ContentParsers
autoload :MeetingParser, "decidim/content_parsers/meeting_parser"
end

module ContentRenderers
autoload :MeetingRenderer, "decidim/content_renderers/meeting_renderer"
end
end
6 changes: 6 additions & 0 deletions decidim-meetings/lib/decidim/meetings/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class Engine < ::Rails::Engine
end
end

initializer "decidim.content_processors" do |_app|
Decidim.configure do |config|
config.content_processors += [:meeting]
end
end

initializer "decidim_meetings.view_hooks" do
Decidim.view_hooks.register(:participatory_space_highlighted_elements, priority: Decidim::ViewHooks::HIGH_PRIORITY) do |view_context|
view_context.cell("decidim/meetings/highlighted_meetings", view_context.current_participatory_space)
Expand Down
Loading

0 comments on commit a6bddc3

Please sign in to comment.