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