FEATURE: Disallow putting urls in the title for TL-0 users (#13947)

FEATURE: Disallow putting urls in the title for TL-0 users (#13947)

This disallows putting URLs in topic titles for TL0 users, which means that:

If a TL-0 user puts a link into the title, a topic featured link won’t be generated (as if it was disabled in the site settings) Server methods for creating and updating topics will be refusing featured links when they are called by TL-0 users TL-0 users won’t be able to put any link into the topic title. For example, the title “Hey, take a look at https://my-site.com” will be rejected.

Also, it improves a bit server behavior when creating or updating feature links on topics in the categories with disabled featured links. Before the server just silently ignored a featured link field that was passed to him, now it will be returning 422 response.

diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js
index f48dda9..d41257b 100644
--- a/app/assets/javascripts/discourse/app/controllers/topic.js
+++ b/app/assets/javascripts/discourse/app/controllers/topic.js
@@ -172,6 +172,10 @@ export default Controller.extend(bufferedProperty("model"), {
 
   @discourseComputed("model.isPrivateMessage", "model.category.id")
   canEditTopicFeaturedLink(isPrivateMessage, categoryId) {
+    if (this.currentUser && this.currentUser.trust_level === 0) {
+      return false;
+    }
+
     if (!this.siteSettings.topic_featured_link_enabled || isPrivateMessage) {
       return false;
     }
diff --git a/app/assets/javascripts/discourse/app/models/composer.js b/app/assets/javascripts/discourse/app/models/composer.js
index b93ab8c..85c69ad 100644
--- a/app/assets/javascripts/discourse/app/models/composer.js
+++ b/app/assets/javascripts/discourse/app/models/composer.js
@@ -280,8 +280,22 @@ const Composer = RestModel.extend({
     "notPrivateMessage"
   ),
 
-  @discourseComputed("canEditTitle", "creatingPrivateMessage", "categoryId")
-  canEditTopicFeaturedLink(canEditTitle, creatingPrivateMessage, categoryId) {
+  @discourseComputed(
+    "canEditTitle",
+    "creatingPrivateMessage",
+    "categoryId",
+    "user.trust_level"
+  )
+  canEditTopicFeaturedLink(
+    canEditTitle,
+    creatingPrivateMessage,
+    categoryId,
+    userTrustLevel
+  ) {
+    if (userTrustLevel === 0) {
+      return false;
+    }
+
     if (
       !this.siteSettings.topic_featured_link_enabled ||
       !canEditTitle ||
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 601c975..ce78ed1 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -164,6 +164,7 @@ class Topic < ActiveRecord::Base
                     topic_title_length: true,
                     censored_words: true,
                     watched_words: true,
+                    urls_in_topic_title: true,
                     quality_title: { unless: :private_message? },
                     max_emojis: true,
                     unique_among: { unless: Proc.new { |t| (SiteSetting.allow_duplicate_topic_titles? || t.private_message?) },
@@ -190,8 +191,9 @@ class Topic < ActiveRecord::Base
 
   validates :featured_link, allow_nil: true, url: true
   validate if: :featured_link do
-    errors.add(:featured_link, :invalid_category) unless !featured_link_changed? ||
-      Guardian.new.can_edit_featured_link?(category_id)
+    if featured_link_changed? && !Guardian.new(user).can_edit_featured_link?(category_id)
+      errors.add(:featured_link)
+    end
   end
 
   before_validation do
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 2d4f846..c5c22c7 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -358,6 +358,7 @@ en:
     other: "Sorry, new users can only put %{count} attachments in a post."
   no_links_allowed: "Sorry, new users can't put links in posts."
   links_require_trust: "Sorry, you can't include links in your posts."
+  urls_in_title_require_trust_level: "Sorry, new users can't include links in topic titles."
   too_many_links:
     one: "Sorry, new users can only put one link in a post."
     other: "Sorry, new users can only put %{count} links in a post."
@@ -579,7 +580,6 @@ en:
               unable_to_tag: "There was an error tagging the topic."
             featured_link:
               invalid: "is invalid. URL should include http:// or https://."
-              invalid_category: "can't be edited in this category."
         user:
           attributes:
             password:
diff --git a/lib/guardian.rb b/lib/guardian.rb
index 54822b9..77e32aa 100644
--- a/lib/guardian.rb
+++ b/lib/guardian.rb
@@ -540,6 +540,10 @@ class Guardian
     !SiteSetting.login_required? || authenticated?
   end
 
+  def can_put_urls_in_topic_title?
+    @user.trust_level >= TrustLevel.levels[:basic]
+  end
+
   def auth_token
     if cookie = request&.cookies[Auth::DefaultCurrentUserProvider::TOKEN_COOKIE]
       UserAuthToken.hash_token(cookie)
diff --git a/lib/guardian/topic_guardian.rb b/lib/guardian/topic_guardian.rb
index 98e5145..a8504cb 100644
--- a/lib/guardian/topic_guardian.rb
+++ b/lib/guardian/topic_guardian.rb
@@ -206,6 +206,7 @@ module TopicGuardian
 
   def can_edit_featured_link?(category_id)
     return false unless SiteSetting.topic_featured_link_enabled
+    return false unless @user.trust_level >= TrustLevel.levels[:basic]
     Category.where(id: category_id || SiteSetting.uncategorized_category_id, topic_featured_link_allowed: true).exists?
   end
 
@@ -251,5 +252,4 @@ module TopicGuardian
   def affected_by_slow_mode?(topic)
     topic&.slow_mode_seconds.to_i > 0 && @user.human? && !is_staff?
   end
-
 end
diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb
index eee8486..1cfc60a 100644
--- a/lib/post_revisor.rb
+++ b/lib/post_revisor.rb
@@ -67,14 +67,28 @@ class PostRevisor
     end
   end
 
-  # Fields we want to record revisions for by default
-  %i{title archetype}.each do |field|
-    track_topic_field(field) do |tc, attribute|
-      tc.record_change(field, tc.topic.public_send(field), attribute)
-      tc.topic.public_send("#{field}=", attribute)
+  def self.track_and_revise(topic_changes, field, attribute)
+    topic_changes.record_change(
+      field,
+      topic_changes.topic.public_send(field),
+      attribute
+    )
+    topic_changes.topic.public_send("#{field}=", attribute)
+  end
+
+  track_topic_field(:title) do |topic_changes, attribute|
+    if UrlHelper.contains_url?(attribute) && !topic_changes.guardian.can_put_urls_in_topic_title?
+      topic_changes.topic.errors.add(:base, I18n.t("urls_in_title_require_trust_level"))
+      topic_changes.check_result(false)
+    else
+      track_and_revise topic_changes, :title, attribute
     end
   end
 
+  track_topic_field(:archetype) do |topic_changes, attribute|
+    track_and_revise topic_changes, :archetype, attribute
+  end
+
   track_topic_field(:category_id) do |tc, category_id, fields|
     if category_id == 0 && tc.topic.private_message?
       tc.record_change('category_id', tc.topic.category_id, nil)
@@ -111,9 +125,10 @@ class PostRevisor
   end
 
   track_topic_field(:featured_link) do |topic_changes, featured_link|
-    if SiteSetting.topic_featured_link_enabled &&
-       topic_changes.guardian.can_edit_featured_link?(topic_changes.topic.category_id)
-
+    if !SiteSetting.topic_featured_link_enabled ||
+      !topic_changes.guardian.can_edit_featured_link?(topic_changes.topic.category_id)
+      topic_changes.check_result(false)
+    else
       topic_changes.record_change('featured_link', topic_changes.topic.featured_link, featured_link)
       topic_changes.topic.featured_link = featured_link
     end
diff --git a/lib/topic_creator.rb b/lib/topic_creator.rb
index 7ba59e9..a250ef6 100644
--- a/lib/topic_creator.rb
+++ b/lib/topic_creator.rb
@@ -132,15 +132,10 @@ class TopicCreator
     end
 
     topic_params[:category_id] = category.id if category.present?
-
     topic_params[:created_at] = convert_time(@opts[:created_at]) if @opts[:created_at].present?
-
     topic_params[:pinned_at] = convert_time(@opts[:pinned_at]) if @opts[:pinned_at].present?
     topic_params[:pinned_globally] = @opts[:pinned_globally] if @opts[:pinned_globally].present?
-
-    if SiteSetting.topic_featured_link_enabled && @opts[:featured_link].present? && @guardian.can_edit_featured_link?(topic_params[:category_id])
-      topic_params[:featured_link] = @opts[:featured_link]
-    end
+    topic_params[:featured_link] = @opts[:featured_link]
 
     topic_params
   end
diff --git a/lib/url_helper.rb b/lib/url_helper.rb
index b1f10df..f55b40e 100644
--- a/lib/url_helper.rb
+++ b/lib/url_helper.rb
@@ -65,6 +65,11 @@ class UrlHelper
     Addressable::URI.normalized_encode(uri)
   end
 
+  def self.contains_url?(string)

[... diff too long, it was truncated ...]

GitHub sha: 0c0a11b66a0ee3d9011c6793558d927297f6edc5

This commit appears in #13947 which was approved by martin. It was merged by AndrewPrigorshnev.