diff --git a/app/jobs/regular/update_username.rb b/app/jobs/regular/update_username.rb index c56ac2ecf20..c9e1367e479 100644 --- a/app/jobs/regular/update_username.rb +++ b/app/jobs/regular/update_username.rb @@ -11,7 +11,10 @@ module Jobs @old_username = args[:old_username].unicode_normalize @new_username = args[:new_username].unicode_normalize - @avatar_img = PrettyText.avatar_img(args[:avatar_template], "tiny") + + avatar_img = PrettyText.avatar_img(args[:avatar_template], "tiny") + + @quote_rewriter = QuoteRewriter.new(@user_id, @old_username, @new_username, avatar_img) @raw_mention_regex = / @@ -26,13 +29,10 @@ module Jobs ) /ix - @raw_quote_regex = /(\[quote\s*=\s*["'']?)#{@old_username}(\,?[^\]]*\])/i - cooked_username = PrettyText::Helpers.format_username(@old_username) @cooked_mention_username_regex = /\A@#{cooked_username}\z/i @cooked_mention_user_path_regex = %r{\A/u(?:sers)?/#{UrlHelper.encode_component(cooked_username)}\z}i - @cooked_quote_username_regex = /(?<=\s)#{cooked_username}(?=:)/i update_posts update_revisions @@ -160,10 +160,7 @@ module Jobs end def update_raw(raw) - raw.gsub(@raw_mention_regex, "@#{@new_username}").gsub( - @raw_quote_regex, - "\\1#{@new_username}\\2", - ) + @quote_rewriter.rewrite_raw(raw.gsub(@raw_mention_regex, "@#{@new_username}")) end # Uses Nokogiri instead of rebake, because it works for posts and revisions @@ -182,28 +179,7 @@ module Jobs ) if a["href"] end - doc - .css("aside.quote") - .each do |aside| - next unless div = aside.at_css("div.title") - - username_replaced = false - - aside["data-username"] = @new_username if aside["data-username"] == @old_username - - div.children.each do |child| - if child.text? - content = child.content - username_replaced = - content.gsub!(@cooked_quote_username_regex, @new_username).present? - child.content = content if username_replaced - end - end - - if username_replaced || quotes_correct_user?(aside) - div.at_css("img.avatar")&.replace(@avatar_img) - end - end + @quote_rewriter.rewrite_cooked(doc) doc.to_html end diff --git a/lib/quote_rewriter.rb b/lib/quote_rewriter.rb new file mode 100644 index 00000000000..abc76504d2c --- /dev/null +++ b/lib/quote_rewriter.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class QuoteRewriter + def initialize(user_id, old_username, new_username, avatar_img) + @user_id = user_id + @old_username = old_username + @new_username = new_username + @avatar_img = avatar_img + end + + def rewrite_raw(raw) + pattern = + Regexp.union( + /(?
\[quote\s*=\s*["'']?.*username:)#{old_username}(?\,?[^\]]*\])/i, + /(? \[quote\s*=\s*["'']?)#{old_username}(?\,?[^\]]*\])/i, + ) + + raw.gsub(pattern, "\\k #{new_username}\\k") + end + + def rewrite_cooked(cooked) + pattern = /(?<=\s)#{PrettyText::Helpers.format_username(old_username)}(?=:)/i + + cooked + .css("aside.quote") + .each do |aside| + next unless div = aside.at_css("div.title") + + username_replaced = false + + aside["data-username"] = new_username if aside["data-username"] == old_username + + div.children.each do |child| + if child.text? + content = child.content + username_replaced = content.gsub!(pattern, new_username).present? + child.content = content if username_replaced + end + end + + if username_replaced || quotes_correct_user?(aside) + div.at_css("img.avatar")&.replace(avatar_img) + end + end + end + + private + + attr_reader :user_id, :old_username, :new_username, :avatar_img + + def quotes_correct_user?(aside) + Post.exists?(topic_id: aside["data-topic"], post_number: aside["data-post"], user_id: user_id) + end +end diff --git a/spec/lib/pretty_text_spec.rb b/spec/lib/pretty_text_spec.rb index 2848fca3a82..8a117ccc991 100644 --- a/spec/lib/pretty_text_spec.rb +++ b/spec/lib/pretty_text_spec.rb @@ -28,6 +28,28 @@ RSpec.describe PrettyText do before { User.stubs(:default_template).returns(default_avatar) } + it "correctly extracts usernames from the new quote format" do + topic = Fabricate(:topic, title: "this is a test topic :slight_smile:") + expected = <<~HTML + + HTML + + expect( + cook( + "[quote=\"Jeff, post:2, topic:#{topic.id}, username:codinghorror\"]\nddd\n[/quote]", + topic_id: 1, + ), + ).to eq(n(expected)) + end + it "do off topic quoting with emoji unescape" do topic = Fabricate(:topic, title: "this is a test topic :slight_smile:") expected = <<~HTML diff --git a/spec/services/username_changer_spec.rb b/spec/services/username_changer_spec.rb index fddd97285b3..3b08f70eaf5 100644 --- a/spec/services/username_changer_spec.rb +++ b/spec/services/username_changer_spec.rb @@ -494,6 +494,41 @@ RSpec.describe UsernameChanger do HTML end + it "replaces the username in new quote format" do + post = create_post_and_change_username(raw: <<~RAW) + Lorem ipsum + + [quote="Foo Bar, post:1, topic:#{quoted_post.topic.id}, username:foo"] + quoted post + [/quote] + + dolor sit amet + RAW + + expect(post.raw).to eq(<<~RAW.strip) + Lorem ipsum + + [quote="Foo Bar, post:1, topic:#{quoted_post.topic.id}, username:bar"] + quoted post + [/quote] + + dolor sit amet + RAW + + expect(post.cooked).to match_html(<<~HTML) + Lorem ipsum
+ +dolor sit amet
+ HTML + end + context "when there is a simple quote" do let(:raw) { <<~RAW } Lorem ipsum @@ -599,7 +634,6 @@ RSpec.describe UsernameChanger do quoted post -