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 -