0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-05 21:16:17 +08:00
discourse/plugins/discourse-policy/jobs/scheduled/check_policy.rb
Alan Guo Xiang Tan 22dfc1b5a8
FIX: Restrict policy reminder notifications by post visibility (#40055)
Policy reminder jobs could create `topic_reminder` notifications for
users who were in the policy group but could not see the policy post.
That allowed restricted policy topics to be referenced by reminder
notifications even though the topic itself remained private.

This commit fixes `Jobs::DiscoursePolicy::CheckPolicy` to check post
visibility for each missing policy user before replacing the reminder
notification. Users who cannot see the post are skipped, while visible
policy users continue to receive the reminder.
2026-05-15 12:46:52 +08:00

131 lines
4.3 KiB
Ruby
Vendored

# frozen_string_literal: true
module Jobs
module DiscoursePolicy
class CheckPolicy < ::Jobs::Scheduled
every 6.hours
def execute(args = nil)
sql = <<~SQL
SELECT p.id
FROM post_policies pp
JOIN posts p ON p.id = pp.post_id
JOIN topics t ON t.id = p.topic_id
WHERE t.deleted_at IS NULL
AND p.deleted_at IS NULL
AND t.archetype = 'regular'
AND (
(reminder = 'weekly' AND last_reminded_at < :weekly)
OR
(reminder = 'daily' AND last_reminded_at < :daily)
)
SQL
post_ids = DB.query_single(sql, weekly: 1.week.ago, daily: 1.day.ago)
if post_ids.size > 0
Post
.where(id: post_ids)
.find_each do |post|
post.post_policy.update(last_reminded_at: Time.zone.now)
missing_users(post).find_each do |user|
next unless user.guardian.can_see?(post)
clear_existing_notification(user, post)
user.notifications.create!(
notification_type: Notification.types[:topic_reminder],
topic_id: post.topic_id,
post_number: post.post_number,
data: { topic_title: post.topic.title, display_username: user.username }.to_json,
high_priority: true,
)
end
users_to_email(post).find_each do |user|
::DiscoursePolicy::PolicyMailer.send_email(user, post)
end
end
end
PostPolicy
.where("next_renew_at < ?", Time.zone.now)
.find_each do |policy|
policy
.policy_users
.accepted
.where("accepted_at < ?", policy.next_renew_at)
.update_all(expired_at: Time.zone.now)
next_renew = policy.renew_start
if policy.renew_days.to_i < 1 &&
!PostPolicy.renew_intervals.keys.include?(policy.renew_interval)
Rails.logger.warn("Invalid policy on post #{policy.post_id}")
elsif next_renew.present?
while next_renew < Time.zone.now
next_renew = calculate_next_renew_date(next_renew, policy)
end
end
policy.update(next_renew_at: next_renew)
end
DB.exec <<~SQL,
UPDATE policy_users pu
SET expired_at = :now
FROM post_policies pp
WHERE pp.id = pu.post_policy_id
AND pp.renew_start IS NULL
AND (pp.renew_days IS NOT NULL OR pp.renew_interval IS NOT NULL)
AND pu.accepted_at IS NOT NULL
AND pu.expired_at IS NULL
AND pu.revoked_at IS NULL
AND
(
(pp.renew_days IS NOT NULL AND pu.accepted_at < :now::timestamp - (INTERVAL '1 day' * pp.renew_days::integer)) OR
(pp.renew_interval = :monthly AND pu.accepted_at < :now::timestamp - (INTERVAL '1 month')) OR
(pp.renew_interval = :quarterly AND pu.accepted_at < :now::timestamp - (INTERVAL '1 month' * 3)) OR
(pp.renew_interval = :yearly AND pu.accepted_at < :now::timestamp - (INTERVAL '1 year'))
)
SQL
now: Time.zone.now,
monthly: PostPolicy.renew_intervals["monthly"],
quarterly: PostPolicy.renew_intervals["quarterly"],
yearly: PostPolicy.renew_intervals["yearly"]
end
def calculate_next_renew_date(date, policy)
case policy.renew_interval
when "monthly"
date + 1.month
when "quarterly"
date + 3.months
when "yearly"
date + 1.year
else
date + policy.renew_days.to_i.days
end
end
def missing_users(post)
post.post_policy.not_accepted_by
end
def users_to_email(post)
post.post_policy.emailed_by
end
def clear_existing_notification(user, post)
existing_notification =
Notification.find_by(
notification_type: Notification.types[:topic_reminder],
topic_id: post.topic_id,
post_number: post.post_number,
user: user,
)
return if existing_notification.blank?
existing_notification.delete
end
end
end
end