otwarchive-symphonyarchive/app/models/collection_item.rb

276 lines
9.5 KiB
Ruby
Raw Normal View History

2026-03-11 22:22:11 +00:00
class CollectionItem < ApplicationRecord
APPROVAL_OPTIONS = [
["", :unreviewed],
[ts("Approved"), :approved],
[ts("Rejected"), :rejected]
]
belongs_to :collection, inverse_of: :collection_items
belongs_to :item, polymorphic: :true, inverse_of: :collection_items, touch: true
belongs_to :work, class_name: "Work", foreign_key: "item_id", inverse_of: :collection_items
belongs_to :bookmark, class_name: "Bookmark", foreign_key: "item_id"
validates_uniqueness_of :collection_id, scope: [:item_id, :item_type],
message: ts("already contains this item.")
enum :user_approval_status, {
rejected: -1,
unreviewed: 0,
approved: 1
}, suffix: :by_user
enum :collection_approval_status, {
rejected: -1,
unreviewed: 0,
approved: 1
}, suffix: :by_collection
validate :collection_is_open, on: :create
def collection_is_open
if self.new_record? && self.collection && self.collection.closed? && !self.collection.user_is_maintainer?(User.current_user)
errors.add(:base, ts("The collection %{title} is not currently open.", title: self.collection.title))
end
end
scope :include_for_works, -> { includes(item: :pseuds) }
scope :unrevealed, -> { where(unrevealed: true) }
scope :anonymous, -> { where(anonymous: true) }
def self.for_user(user=User.current_user)
# get ids of user's bookmarks and works
bookmark_ids = Bookmark.joins(:pseud).where("pseuds.user_id = ?", user.id).pluck(:id)
work_ids = Work.joins(:pseuds).where("pseuds.user_id = ?", user.id).pluck(:id)
# now return the relation
where("(item_id IN (?) AND item_type = 'Work') OR (item_id IN (?) AND item_type = 'Bookmark')", work_ids, bookmark_ids)
end
scope :invited_by_collection, -> { approved_by_collection.unreviewed_by_user }
scope :approved_by_both, -> { approved_by_collection.approved_by_user }
before_save :set_anonymous_and_unrevealed
def set_anonymous_and_unrevealed
if self.new_record? && collection
self.unrevealed = true if collection.reload.unrevealed?
self.anonymous = true if collection.reload.anonymous?
end
end
after_save :update_work
after_destroy :update_work
# Set associated works to anonymous or unrevealed as appropriate.
#
# Inverses are set up properly on self.item, so we use that field to check
# whether we're currently in the process of saving a brand new work, or
# whether the work is in the process of being destroyed. (In which case we
# rely on the Work's callbacks to set anon/unrevealed status properly.) But
# because we want to discard changes made in preview mode, we perform the
# actual anon/unrevealed updates on self.work, which doesn't have proper
# inverses and therefore is freshly loaded from the database.
def update_work
return unless item.is_a?(Work) && item.persisted? && !item.saved_change_to_id?
if work.present?
work.update_anon_unrevealed
# For a more helpful error message, raise an error saying that the work
# is invalid if we fail to save it.
raise ActiveRecord::RecordInvalid, work unless work.save(validate: false)
end
end
# Poke the item if it's just been approved or unapproved so it gets picked up by the search index
after_update :update_item_for_status_change
def update_item_for_status_change
if saved_change_to_user_approval_status? || saved_change_to_collection_approval_status?
item.save!(validate: false)
end
end
after_create_commit :notify_of_association
def notify_of_association
email_notify = self.collection.collection_preference &&
self.collection.collection_preference.email_notify
if email_notify && !self.collection.email.blank?
CollectionMailer.item_added_notification(item_id, collection_id, item_type).deliver_later
end
end
after_create_commit :notify_archivist_added
# Sends emails to item creator(s) in the case that an archivist
# has added them to the collection.
def notify_archivist_added
return unless item.is_a?(Work) && User.current_user&.archivist && collection.user_is_maintainer?(User.current_user)
item.users.each do |email_recipient|
next if email_recipient.preference.collection_emails_off
I18n.with_locale(email_recipient.preference.locale_for_mails) do
UserMailer.archivist_added_to_collection_notification(
email_recipient.id,
item.id,
collection.id
).deliver_later
end
end
end
before_save :approve_automatically
def approve_automatically
return unless self.new_record?
# approve with the current user, who is the person who has just
# added this item -- might be either moderator or owner
# rubocop:disable Lint/BooleanSymbol
approve(User.current_user == :false ? nil : User.current_user)
# rubocop:enable Lint/BooleanSymbol
# if the collection is open or the user who owns this work is a member, go ahead and approve
# for the collection
return unless !approved_by_collection? && collection
approve_by_collection if !collection.moderated? || collection.user_is_maintainer?(User.current_user) || collection.user_is_posting_participant?(User.current_user)
end
before_save :send_work_invitation
def send_work_invitation
return if approved_by_user? || !approved_by_collection? || !self.new_record? || User.current_user.is_author_of?(item)
# a maintainer is attempting to add this work to their collection
# so we send an email to all the works owners
item.users.each do |email_author|
next if email_author.preference.collection_emails_off
I18n.with_locale(email_author.preference.locale_for_mails) do
UserMailer.invited_to_collection_notification(email_author.id, item.id, collection.id).deliver_now
end
end
end
after_destroy :expire_caches
def expire_caches
if self.item.respond_to?(:expire_caches)
self.item.expire_caches
CacheMaster.record(item_id, 'collection', collection_id)
end
end
attr_writer :remove
def remove
@remove || ""
end
def recipients
item.respond_to?(:recipients) ? item.recipients : ""
end
def item_creator_pseuds
if self.item
if self.item.respond_to?(:pseuds)
self.item.pseuds
elsif self.item.respond_to?(:pseud)
[self.item.pseud]
else
[]
end
else
[]
end
end
def item_date
item.respond_to?(:revised_at) ? item.revised_at : item.updated_at
end
def user_allowed_to_destroy?(user)
user.is_author_of?(self.item) ||
(self.collection.user_is_maintainer?(user) && !self.rejected_by_user?)
end
def approve_by_user
self.user_approval_status = :approved
end
def approve_by_collection
self.collection_approval_status = :approved
end
def approved?
approved_by_user? && approved_by_collection?
end
def approve(user)
if user.nil?
# this is being run via rake task eg for importing collections
approve_by_user
approve_by_collection
else
author_of_item = user.is_author_of?(item) ||
(user == User.current_user && item.respond_to?(:pseuds) ? item.pseuds.empty? : item.pseud.nil?)
archivist_maintainer = user.archivist && self.collection.user_is_maintainer?(user)
approve_by_user if author_of_item || archivist_maintainer
approve_by_collection if self.collection.user_is_maintainer?(user)
end
end
def posted?
self.item.respond_to?(:posted?) ? self.item.posted? : true
end
def notify_of_reveal
unless self.unrevealed? || !self.posted?
recipient_pseuds = Pseud.parse_bylines(self.recipients)[:pseuds]
recipient_pseuds.each do |pseud|
user_preference = pseud.user.preference
next if user_preference.recipient_emails_off
I18n.with_locale(user_preference.locale_for_mails) do
UserMailer.recipient_notification(pseud.user.id, self.item.id, self.collection.id).deliver_after_commit
end
end
# also notify prompters of responses to their prompt
if item_type == "Work" && !item.challenge_claims.blank?
UserMailer.prompter_notification(self.item.id, self.collection.id).deliver_after_commit
end
# also notify the owners of any parent/inspired-by works
if item_type == "Work" && !item.parent_work_relationships.empty?
item.parent_work_relationships.each do |relationship|
relationship.notify_parent_owners
end
end
end
end
after_update :notify_of_unrevealed_or_anonymous
def notify_of_unrevealed_or_anonymous
# This CollectionItem's anonymous/unrevealed status can only affect the
# item's status if (a) the CollectionItem is approved by the user and (b)
# the item is a work. (Bookmarks can't be anonymous/unrevealed at the
# moment.)
return unless approved_by_user? && item.is_a?(Work)
# Check whether anonymous/unrevealed is becoming true, when the work
# currently has it set to false:
newly_anonymous = (saved_change_to_anonymous?(to: true) && !item.anonymous?)
newly_unrevealed = (saved_change_to_unrevealed?(to: true) && !item.unrevealed?)
return unless newly_unrevealed || newly_anonymous
# Don't notify if it's one of the work creators who is changing the work's
# status.
return if item.users.include?(User.current_user)
item.users.each do |user|
I18n.with_locale(user.preference.locale_for_mails) do
UserMailer.anonymous_or_unrevealed_notification(
user.id, item.id, collection.id,
anonymous: newly_anonymous, unrevealed: newly_unrevealed
).deliver_after_commit
end
end
end
end