Class: PostCreator
Overview
Responsible for creating posts and topics
Instance Attribute Summary collapse
-
#opts ⇒ Object
readonly
Returns the value of attribute opts.
-
#post ⇒ Object
readonly
Returns the value of attribute post.
Attributes included from HasErrors
#conflict, #forbidden, #not_found
Class Method Summary collapse
- .before_create_tasks(post) ⇒ Object
- .create(user, opts) ⇒ Object
- .create!(user, opts) ⇒ Object
- .set_reply_info(post) ⇒ Object
- .track_post_stats ⇒ Object
- .track_post_stats=(val) ⇒ Object
Instance Method Summary collapse
- #create ⇒ Object
- #create! ⇒ Object
- #enqueue_jobs ⇒ Object
- #guardian ⇒ Object
-
#initialize(user, opts) ⇒ PostCreator
constructor
Acceptable options:.
- #pg_clean_up(str) ⇒ Object
- #skip_validations? ⇒ Boolean
- #spam? ⇒ Boolean
- #trigger_after_events ⇒ Object
- #valid? ⇒ Boolean
Methods included from HasErrors
#add_error, #add_errors_from, #errors, #rollback_from_errors!, #rollback_with!, #validate_child
Constructor Details
#initialize(user, opts) ⇒ PostCreator
Acceptable options:
raw - raw text of post
image_sizes - We can pass a list of the sizes of images in the post as a shortcut.
invalidate_oneboxes - Whether to force invalidation of oneboxes in this post
acting_user - The user performing the action might be different than the user
who is the post "author." For example when copying posts to a new
topic.
created_at - Post creation time (optional)
auto_track - Automatically track this topic if needed (default true)
custom_fields - Custom fields to be added to the post, Hash (default nil)
post_type - Whether this is a regular post or moderator post.
no_bump - Do not cause this post to bump the topic.
cooking_options - Options for rendering the text
cook_method - Method of cooking the post.
:regular - Pass through Markdown parser and strip bad HTML
:raw_html - Perform no processing
:raw_email - Imported from an email
via_email - Mark this post as arriving via email
raw_email - Full text of arriving email (to store)
action_code - Describes a small_action post (optional)
skip_jobs - Don't enqueue jobs when creation succeeds. This is needed if you
wrap `PostCreator` in a transaction, as the sidekiq jobs could
dequeue before the commit finishes. If you do this, be sure to
call `enqueue_jobs` after the transaction is committed.
hidden_reason_id - Reason for hiding the post (optional)
skip_validations - Do not validate any of the content in the post
draft_key - the key of the draft we are creating (will be deleted on success)
advance_draft - Destroy draft after creating post or topic
silent - Do not update topic stats and fields like last_post_user_id
When replying to a topic:
topic_id - topic we're replying to
reply_to_post_number - post number we're replying to
When creating a topic:
title - New topic title
archetype - Topic archetype
is_warning - Is the topic a warning?
category - Category to assign to topic
target_usernames - comma delimited list of usernames for membership (private message)
target_group_names - comma delimited list of groups for membership (private message)
created_at - Topic creation time (optional)
pinned_at - Topic pinned time (optional)
pinned_globally - Is the topic pinned globally (optional)
shared_draft - Is the topic meant to be a shared draft
topic_opts - Options to be overwritten for topic
embed_url - Creates a TopicEmbed for the topic
embed_content_sha1 - Sets the content_sha1 of the TopicEmbed
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/post_creator.rb', line 61 def initialize(user, opts) # TODO: we should reload user in case it is tainted, should take in a user_id as opposed to user # If we don't do this we introduce a rather risky dependency @user = user @spam = false @opts = opts || {} opts[:title] = pg_clean_up(opts[:title]) if opts[:title]&.include?("\u0000") opts[:raw] = pg_clean_up(opts[:raw]) if opts[:raw]&.include?("\u0000") opts[:visible] = false if ( (opts[:visible].nil? && opts[:hidden_reason_id].present?) || (opts[:embed_url].present? && SiteSetting.) ) opts.delete(:reply_to_post_number) unless opts[:topic_id] end |
Instance Attribute Details
#opts ⇒ Object (readonly)
Returns the value of attribute opts.
9 10 11 |
# File 'lib/post_creator.rb', line 9 def opts @opts end |
#post ⇒ Object (readonly)
Returns the value of attribute post.
9 10 11 |
# File 'lib/post_creator.rb', line 9 def post @post end |
Class Method Details
.before_create_tasks(post) ⇒ Object
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/post_creator.rb', line 277 def self.before_create_tasks(post) set_reply_info(post) post.word_count = post.raw.scan(/[[:word:]]+/).size whisper = post.post_type == Post.types[:whisper] increase_posts_count = !post.topic&. || post.post_type != Post.types[:small_action] post.post_number ||= Topic.next_post_number( post.topic_id, reply: post.reply_to_post_number.present?, whisper: whisper, post: increase_posts_count, ) = post. || {} [:topic_id] = post.topic_id post.cooked ||= post.cook(post.raw, .symbolize_keys) post.sort_order = post.post_number post.last_version_at ||= Time.now end |
.create(user, opts) ⇒ Object
269 270 271 |
# File 'lib/post_creator.rb', line 269 def self.create(user, opts) PostCreator.new(user, opts).create end |
.create!(user, opts) ⇒ Object
273 274 275 |
# File 'lib/post_creator.rb', line 273 def self.create!(user, opts) PostCreator.new(user, opts).create! end |
.set_reply_info(post) ⇒ Object
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/post_creator.rb', line 301 def self.set_reply_info(post) return if post.reply_to_post_number.blank? # Before the locking here was added, replying to a post and liking a post # at roughly the same time could cause a deadlock. # # Liking a post grabs an update lock on the post and then on the topic (to # update like counts). # # Here, we lock the replied to post before getting the topic lock so that # we can update the replied to post later without causing a deadlock. reply_info = Post .where(topic_id: post.topic_id, post_number: post.reply_to_post_number) .select(:user_id, :post_type) .lock .first if reply_info.present? post.reply_to_user_id ||= reply_info.user_id whisper_type = Post.types[:whisper] post.post_type = whisper_type if reply_info.post_type == whisper_type end end |
.track_post_stats ⇒ Object
261 262 263 |
# File 'lib/post_creator.rb', line 261 def self.track_post_stats Rails.env != "test" || @track_post_stats end |
.track_post_stats=(val) ⇒ Object
265 266 267 |
# File 'lib/post_creator.rb', line 265 def self.track_post_stats=(val) @track_post_stats = val end |
Instance Method Details
#create ⇒ Object
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/post_creator.rb', line 186 def create if valid? transaction do build_post_stats create_topic create_post_notice save_post UserActionManager.post_created(@post) extract_links track_topic update_topic_stats update_topic_auto_close update_user_counts @post.link_post_uploads delete_owned_bookmarks ensure_in_allowed_users if guardian.is_staff? if !@opts[:import_mode] DraftSequence.next!(@user, draft_key) if !@opts[:import_mode] && @opts[:advance_draft] @post.save_reply_relationships end end if @post && errors.blank? && !@opts[:import_mode] store_unique_post_key # update counters etc. @post.topic.reload publish track_latest_on_category enqueue_jobs unless @opts[:skip_jobs] BadgeGranter.queue_badge_grant(Badge::Trigger::PostRevision, post: @post) trigger_after_events unless opts[:skip_events] auto_close end if !opts[:import_mode] && !opts[:reviewed_queued_post] handle_spam if (@spam || @post) ReviewablePost.queue_for_review_if_possible(@post, @user) if !@spam && @post && errors.blank? end @post end |
#create! ⇒ Object
234 235 236 237 238 239 240 241 242 |
# File 'lib/post_creator.rb', line 234 def create! create if !self.errors..empty? raise ActiveRecord::RecordNotSaved.new(self.errors..to_sentence) end @post end |
#enqueue_jobs ⇒ Object
244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/post_creator.rb', line 244 def enqueue_jobs return unless @post && !@post.errors.present? PostJobsEnqueuer.new( @post, @topic, new_topic?, import_mode: @opts[:import_mode], post_alert_options: @opts[:post_alert_options], ).enqueue_jobs end |
#guardian ⇒ Object
90 91 92 |
# File 'lib/post_creator.rb', line 90 def guardian @guardian ||= @opts[:guardian] || Guardian.new(@user) end |
#pg_clean_up(str) ⇒ Object
78 79 80 |
# File 'lib/post_creator.rb', line 78 def pg_clean_up(str) str.gsub("\u0000", "") end |
#skip_validations? ⇒ Boolean
86 87 88 |
# File 'lib/post_creator.rb', line 86 def skip_validations? @opts[:skip_validations] end |
#spam? ⇒ Boolean
82 83 84 |
# File 'lib/post_creator.rb', line 82 def spam? @spam end |
#trigger_after_events ⇒ Object
256 257 258 259 |
# File 'lib/post_creator.rb', line 256 def trigger_after_events DiscourseEvent.trigger(:topic_created, @post.topic, @opts, @user) unless @opts[:topic_id] DiscourseEvent.trigger(:post_created, @post, @opts, @user) end |
#valid? ⇒ Boolean
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/post_creator.rb', line 94 def valid? @topic = nil @post = nil if @user.suspended? && !skip_validations? errors.add(:base, I18n.t(:user_is_suspended)) return false end if @opts[:target_usernames].present? && !skip_validations? && !@user.staff? names = @opts[:target_usernames].split(",").flatten.map(&:downcase) # Make sure max_allowed_message_recipients setting is respected = SiteSetting. if names.length > errors.add( :base, I18n.t(:max_pm_recipients, recipients_limit: ), ) return false end # Make sure none of the users have muted or ignored the creator or prevented # PMs from being sent to them target_users = User.where(username_lower: names.map(&:downcase)).pluck(:id, :username).to_h UserCommScreener .new(acting_user: @user, target_user_ids: target_users.keys) .preventing_actor_communication .each do |user_id| errors.add(:base, I18n.t(:not_accepting_pms, username: target_users[user_id])) end return false if errors[:base].present? end if new_topic? topic_creator = TopicCreator.new(@user, guardian, @opts) return false unless skip_validations? || validate_child(topic_creator) else @topic = Topic.find_by(id: @opts[:topic_id]) if @topic.present? && @opts[:archetype] == Archetype. errors.add(:base, I18n.t(:create_pm_on_existing_topic)) return false end if guardian.affected_by_slow_mode?(@topic) tu = TopicUser.find_by(user: @user, topic: @topic) if tu&.last_posted_at threshold = tu.last_posted_at + @topic.slow_mode_seconds.seconds if DateTime.now < threshold errors.add(:base, I18n.t(:slow_mode_enabled)) return false end end end if @topic.blank? || !(@opts[:skip_guardian] || guardian.can_create?(Post, @topic)) errors.add(:base, I18n.t(:topic_not_found)) return false end end setup_post return true if skip_validations? if @post.has_host_spam? @spam = true errors.add(:base, I18n.t(:spamming_host)) return false end DiscourseEvent.trigger :before_create_post, @post, @opts DiscourseEvent.trigger :validate_post, @post post_validator = PostValidator.new( skip_topic: true, private_message: @opts[:archetype] == Archetype., ) post_validator.validate(@post) valid = @post.errors.blank? add_errors_from(@post) unless valid valid end |