discourse/plugins/automation/lib/discourse_automation/event_handlers.rb
Joffrey JAFFEUX ad7d5426d8
FIX: supports groups field in post_created_edited (#28783)
⚠️ This commit is a revert of a revert due to a migration which was causing `{}` metadata to be transformed into `{"value": [null]}`. The new migration shouldn't cause this and will also clean the existing errors, there shouldn't  be any data loss given the affected fields where not containing actual data. We might want to stop storing these empty fields in the future.

To achieve it, this commit does the following:
- create a new `groups field`, ideally we would have reused the existing group field, but many automations now have the expectation that this field will return a group id and not an array of group ids, which makes it a dangerous change
- alter the code in `post_created_edited` to use this new groups field and change the logic to use an array
- migrate the existing group fields post_created_edited automations to change name from `restricted_group` to `restricted_groups`, the component from `group` to `groups` and the metadata from `{"value": integer}` to `{"value": [integer]}`

<!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
2024-09-06 17:22:42 +02:00

366 lines
13 KiB
Ruby

# frozen_string_literal: true
module DiscourseAutomation
module EventHandlers
def self.handle_post_created_edited(post, action)
return if post.post_type != Post.types[:regular] || post.user_id < 0
topic = post.topic
return if topic.blank?
name = DiscourseAutomation::Triggers::POST_CREATED_EDITED
DiscourseAutomation::Automation
.where(trigger: name, enabled: true)
.find_each do |automation|
original_post_only = automation.trigger_field("original_post_only")
if original_post_only["value"]
next if topic.posts_count > 1
end
first_post_only = automation.trigger_field("first_post_only")
if first_post_only["value"]
next if post.user.user_stat.post_count != 1
end
first_topic_only = automation.trigger_field("first_topic_only")
if first_topic_only["value"]
next if post.post_number != 1
next if post.user.user_stat.topic_count != 1
end
skip_via_email = automation.trigger_field("skip_via_email")
if skip_via_email["value"]
next if post.via_email?
end
valid_trust_levels = automation.trigger_field("valid_trust_levels")
if valid_trust_levels["value"]
next if valid_trust_levels["value"].exclude?(post.user.trust_level)
end
restricted_category = automation.trigger_field("restricted_category")
if restricted_category["value"]
category_ids =
if topic.category_id.blank?
[]
else
[topic.category_id, topic.category.parent_category_id]
end
next if !category_ids.include?(restricted_category["value"])
end
restricted_tags = automation.trigger_field("restricted_tags")
if restricted_tags["value"]
next if (restricted_tags["value"] & topic.tags.map(&:name)).empty?
end
restricted_group_ids = automation.trigger_field("restricted_groups")["value"]
if restricted_group_ids.present?
next if !topic.private_message?
target_group_ids = topic.allowed_groups.pluck(:id)
next if (restricted_group_ids & target_group_ids).empty?
ignore_group_members = automation.trigger_field("ignore_group_members")["value"]
next if ignore_group_members && post.user.in_any_groups?(restricted_group_ids)
end
ignore_automated = automation.trigger_field("ignore_automated")
next if ignore_automated["value"] && post.incoming_email&.is_auto_generated?
action_type = automation.trigger_field("action_type")
selected_action = action_type["value"]&.to_sym
if selected_action
next if selected_action == :created && action != :create
next if selected_action == :edited && action != :edit
end
automation.trigger!(
"kind" => name,
"action" => action,
"post" => post,
"placeholders" => {
"topic_url" => topic.relative_url,
"topic_title" => topic.title,
},
)
end
end
def self.handle_user_updated(user, new_user: false)
return if user.id < 0
name = DiscourseAutomation::Triggers::USER_UPDATED
DiscourseAutomation::Automation
.where(trigger: name, enabled: true)
.find_each do |automation|
once_per_user = automation.trigger_field("once_per_user")["value"]
if once_per_user &&
user.custom_fields[
DiscourseAutomation::AUTOMATION_IDS_CUSTOM_FIELD
].presence&.include?(automation.id)
next
end
new_users_only = automation.trigger_field("new_users_only")["value"]
new_user_custom_field = automation.new_user_custom_field_name
new_user ||= user.custom_fields[new_user_custom_field].present?
next if new_users_only && !new_user
required_custom_fields = automation.trigger_field("custom_fields")
user_data = {}
user_custom_fields_data = DB.query <<-SQL
SELECT uf.name AS field_name, ucf.value AS field_value
FROM user_fields uf
JOIN user_custom_fields ucf ON CONCAT('user_field_', uf.id) = ucf.name
WHERE ucf.user_id = #{user.id};
SQL
user_custom_fields_data =
user_custom_fields_data.each_with_object({}) do |obj, hash|
field_name = obj.field_name
field_value = obj.field_value
hash[field_name] = field_value
end
if required_custom_fields["value"]
if required_custom_fields["value"].any? { |field|
user_custom_fields_data[field].blank?
}
if new_users_only
user.custom_fields[new_user_custom_field] = "1"
user.save_custom_fields
end
next
end
user_data[:custom_fields] = user_custom_fields_data
end
required_user_profile_fields = automation.trigger_field("user_profile")
user_profile_data = UserProfile.find(user.id).attributes
if required_user_profile_fields["value"]
if required_user_profile_fields["value"].any? { |field|
user_profile_data[field].blank?
}
if new_users_only
user.custom_fields[new_user_custom_field] = "1"
user.save_custom_fields
end
next
end
user_data[:profile_data] = user_profile_data
end
if new_users_only && once_per_user
user.custom_fields.delete(new_user_custom_field)
user.save_custom_fields
end
automation.add_id_to_custom_field(user, DiscourseAutomation::AUTOMATION_IDS_CUSTOM_FIELD)
automation.trigger!("kind" => name, "user" => user, "user_data" => user_data)
end
end
def self.handle_category_created_edited(category, action)
name = DiscourseAutomation::Triggers::CATEGORY_CREATED_EDITED
DiscourseAutomation::Automation
.where(trigger: name, enabled: true)
.find_each do |automation|
restricted_category = automation.trigger_field("restricted_category")
if restricted_category["value"].present?
next if restricted_category["value"] != category.parent_category_id
end
automation.trigger!("kind" => name, "action" => action, "category" => category)
end
end
def self.handle_pm_created(topic)
return if topic.user_id < 0
user = topic.user
target_usernames = topic.allowed_users.pluck(:username) - [user.username]
target_group_ids = topic.allowed_groups.pluck(:id)
return if (target_usernames.length + target_group_ids.length) > 1
name = DiscourseAutomation::Triggers::PM_CREATED
DiscourseAutomation::Automation
.where(trigger: name, enabled: true)
.find_each do |automation|
restricted_username = automation.trigger_field("restricted_user")["value"]
next if restricted_username.present? && restricted_username != target_usernames.first
restricted_group_id = automation.trigger_field("restricted_group")["value"]
next if restricted_group_id.present? && restricted_group_id != target_group_ids.first
ignore_staff = automation.trigger_field("ignore_staff")
next if ignore_staff["value"] && user.staff?
ignore_group_members = automation.trigger_field("ignore_group_members")
next if ignore_group_members["value"] && user.in_any_groups?([restricted_group_id])
ignore_automated = automation.trigger_field("ignore_automated")
next if ignore_automated["value"] && topic.first_post.incoming_email&.is_auto_generated?
valid_trust_levels = automation.trigger_field("valid_trust_levels")
if valid_trust_levels["value"]
next if !valid_trust_levels["value"].include?(user.trust_level)
end
automation.trigger!("kind" => name, "post" => topic.first_post)
end
end
def self.handle_topic_tags_changed(topic, old_tag_names, new_tag_names, user)
name = DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED
DiscourseAutomation::Automation
.where(trigger: name, enabled: true)
.find_each do |automation|
watching_categories = automation.trigger_field("watching_categories")
if watching_categories["value"]
next if !watching_categories["value"].include?(topic.category_id)
end
removed_tags = old_tag_names - new_tag_names
added_tags = new_tag_names - old_tag_names
watching_tags = automation.trigger_field("watching_tags")
if watching_tags["value"]
should_skip = false
watching_tags["value"].each do |tag|
should_skip = true if !removed_tags.empty? && !removed_tags.include?(tag)
should_skip = true if !added_tags.empty? && !added_tags.include?(tag)
end
next if should_skip
end
automation.trigger!(
"kind" => name,
"topic" => topic,
"removed_tags" => removed_tags,
"added_tags" => added_tags,
"user" => user,
"placeholders" => {
"topic_url" => topic.relative_url,
"topic_title" => topic.title,
},
)
end
end
def self.handle_after_post_cook(post, cooked)
return cooked if post.post_type != Post.types[:regular] || post.post_number > 1
name = DiscourseAutomation::Triggers::AFTER_POST_COOK
DiscourseAutomation::Automation
.where(trigger: name, enabled: true)
.find_each do |automation|
valid_trust_levels = automation.trigger_field("valid_trust_levels")
if valid_trust_levels["value"]
next if valid_trust_levels["value"].exclude?(post.user.trust_level)
end
restricted_category = automation.trigger_field("restricted_category")
if restricted_category["value"]
category_ids = [post.topic&.category&.parent_category&.id, post.topic&.category&.id]
next if !category_ids.compact.include?(restricted_category["value"])
end
restricted_tags = automation.trigger_field("restricted_tags")
if tag_names = restricted_tags["value"]
found = false
next if !post.topic
post.topic.tags.each do |tag|
found ||= tag_names.include?(tag.name)
break if found
end
next if !found
end
if new_cooked = automation.trigger!("kind" => name, "post" => post, "cooked" => cooked)
cooked = new_cooked
end
end
cooked
end
def self.handle_user_promoted(user_id, new_trust_level, old_trust_level)
trigger = DiscourseAutomation::Triggers::USER_PROMOTED
user = User.find_by(id: user_id)
return if user.blank?
# don't want to do anything if the user is demoted. this should probably
# be a separate event in core
return if new_trust_level < old_trust_level
DiscourseAutomation::Automation
.where(trigger: trigger, enabled: true)
.find_each do |automation|
trust_level_code_all = DiscourseAutomation::USER_PROMOTED_TRUST_LEVEL_CHOICES.first[:id]
restricted_group_id = automation.trigger_field("restricted_group")["value"]
trust_level_transition = automation.trigger_field("trust_level_transition")["value"]
trust_level_transition = trust_level_transition || trust_level_code_all
if restricted_group_id.present? &&
!GroupUser.exists?(user_id: user_id, group_id: restricted_group_id)
next
end
transition_code = "TL#{old_trust_level}#{new_trust_level}"
if trust_level_transition == trust_level_code_all ||
trust_level_transition == transition_code
automation.trigger!(
"kind" => trigger,
"usernames" => [user.username],
"placeholders" => {
"trust_level_transition" =>
I18n.t(
"discourse_automation.triggerables.user_promoted.transition_placeholder",
from_level_name: TrustLevel.name(old_trust_level),
to_level_name: TrustLevel.name(new_trust_level),
),
},
)
end
end
end
def self.handle_stalled_topic(post)
return if post.topic.blank?
return if post.user_id != post.topic.user_id
DiscourseAutomation::Automation
.where(trigger: DiscourseAutomation::Triggers::STALLED_TOPIC)
.where(enabled: true)
.find_each do |automation|
fields = automation.serialized_fields
categories = fields.dig("categories", "value")
next if categories && !categories.include?(post.topic.category_id)
tags = fields.dig("tags", "value")
next if tags&.any? && (tags & post.topic.tags.map(&:name)).empty?
DiscourseAutomation::UserGlobalNotice
.where(identifier: automation.id)
.where(user_id: post.user_id)
.destroy_all
end
end
end
end