From 2f9d4533c582d4c575def9e2dbfe9eabe4b1f54c Mon Sep 17 00:00:00 2001 From: "Stanko K.R." Date: Tue, 24 Feb 2026 08:22:01 +0100 Subject: [PATCH] Relativize absolute links to the instance on export --- .../action_text/rich_text_record_set.rb | 30 +++++++++-- .../action_text/rich_text_record_set_test.rb | 53 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/app/models/account/data_transfer/action_text/rich_text_record_set.rb b/app/models/account/data_transfer/action_text/rich_text_record_set.rb index e29abd011..01e046dbe 100644 --- a/app/models/account/data_transfer/action_text/rich_text_record_set.rb +++ b/app/models/account/data_transfer/action_text/rich_text_record_set.rb @@ -20,7 +20,7 @@ class Account::DataTransfer::ActionText::RichTextRecordSet < Account::DataTransf end def export_record(rich_text) - data = rich_text.as_json.merge("body" => convert_sgids_to_gids(rich_text.body)) + data = rich_text.as_json.merge("body" => transform_body_for_export(rich_text.body)) zip.add_file "data/action_text_rich_texts/#{rich_text.id}.json", data.to_json end @@ -54,9 +54,14 @@ class Account::DataTransfer::ActionText::RichTextRecordSet < Account::DataTransf check_associations_dont_exist(data) end - def convert_sgids_to_gids(content) + def transform_body_for_export(content) return nil if content.blank? + html = convert_sgids_to_gids(content) + relativize_urls(html) + end + + def convert_sgids_to_gids(content) content.send(:attachment_nodes).each do |node| sgid = SignedGlobalID.parse(node["sgid"], for: ::ActionText::Attachable::LOCATOR_NAME) @@ -75,6 +80,25 @@ class Account::DataTransfer::ActionText::RichTextRecordSet < Account::DataTransf content.fragment.source.to_html end + def relativize_urls(html) + host = Rails.application.routes.default_url_options[:host] + return html unless host + + fragment = Nokogiri::HTML.fragment(html) + + fragment.css("a[href]").each do |link| + uri = URI.parse(link["href"]) rescue nil + + if uri.respond_to?(:host) && uri.host == host + link["href"] = uri.path + link["href"] += "?#{uri.query}" if uri.query + link["href"] += "##{uri.fragment}" if uri.fragment + end + end + + fragment.to_html + end + def transform_body_for_import(body) return body if body.blank? @@ -112,7 +136,7 @@ class Account::DataTransfer::ActionText::RichTextRecordSet < Account::DataTransf if match path = match.post_match.presence || "/" valid_path = Rails.application.routes.recognize_path(path) rescue nil - link["href"] = "#{account.slug}#{remaining_path}" if valid_path + link["href"] = "#{account.slug}#{path}" if valid_path end end diff --git a/test/models/account/data_transfer/action_text/rich_text_record_set_test.rb b/test/models/account/data_transfer/action_text/rich_text_record_set_test.rb index bee51869c..3fed8126d 100644 --- a/test/models/account/data_transfer/action_text/rich_text_record_set_test.rb +++ b/test/models/account/data_transfer/action_text/rich_text_record_set_test.rb @@ -117,4 +117,57 @@ class Account::DataTransfer::ActionText::RichTextRecordSetTest < ActiveSupport:: assert_equal html, result end + + test "relativize_urls strips instance host from absolute URLs" do + with_default_url_host("fizzy.example.com") do + record_set = Account::DataTransfer::ActionText::RichTextRecordSet.new(accounts(:"37s")) + + html = %(

See card

) + result = record_set.send(:relativize_urls, html) + + assert_includes result, %(/123/cards/42) + assert_not_includes result, "fizzy.example.com" + end + end + + test "relativize_urls preserves query and fragment" do + with_default_url_host("fizzy.example.com") do + record_set = Account::DataTransfer::ActionText::RichTextRecordSet.new(accounts(:"37s")) + + html = %(

link

) + result = record_set.send(:relativize_urls, html) + + assert_includes result, "/123/cards/42?tab=comments#comment_1" + assert_not_includes result, "fizzy.example.com" + end + end + + test "relativize_urls leaves external URLs alone" do + with_default_url_host("fizzy.example.com") do + record_set = Account::DataTransfer::ActionText::RichTextRecordSet.new(accounts(:"37s")) + + html = %(

link

) + result = record_set.send(:relativize_urls, html) + + assert_includes result, "https://github.com/some/repo" + end + end + + test "relativize_urls is a no-op when host is not configured" do + record_set = Account::DataTransfer::ActionText::RichTextRecordSet.new(accounts(:"37s")) + + html = %(

link

) + result = record_set.send(:relativize_urls, html) + + assert_includes result, "https://fizzy.example.com/123/cards/42" + end + + private + def with_default_url_host(host) + original = Rails.application.routes.default_url_options[:host] + Rails.application.routes.default_url_options[:host] = host + yield + ensure + Rails.application.routes.default_url_options[:host] = original + end end