kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
5.3 KiB
152 lines
5.3 KiB
#-- encoding: UTF-8
|
|
#-- copyright
|
|
# OpenProject is an open source project management software.
|
|
# Copyright (C) 2012-2021 the OpenProject GmbH
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License version 3.
|
|
#
|
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
|
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
|
# Copyright (C) 2010-2013 the ChiliProject Team
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# See docs/COPYRIGHT.rdoc for more details.
|
|
#++
|
|
|
|
class Watcher < ApplicationRecord
|
|
belongs_to :watchable, polymorphic: true
|
|
belongs_to :user
|
|
|
|
validates_presence_of :watchable, :user
|
|
validates_uniqueness_of :user_id, scope: [:watchable_type, :watchable_id]
|
|
|
|
validate :validate_active_user
|
|
validate :validate_user_allowed_to_watch
|
|
|
|
def self.prune(user: [], project_id: nil)
|
|
user_ids = Array(user).compact.map { |u| u.is_a?(User) ? u.id : nil }.compact
|
|
|
|
projects = project_id ? Project.where(id: project_id) : Project.all
|
|
|
|
prune_project_related(user_ids, projects)
|
|
end
|
|
|
|
protected
|
|
|
|
def validate_active_user
|
|
# TODO add informative error message
|
|
return if user.blank?
|
|
errors.add :user_id, :invalid unless user.active_or_registered?
|
|
end
|
|
|
|
def validate_user_allowed_to_watch
|
|
# TODO add informative error message
|
|
return if user.blank? || watchable.blank?
|
|
errors.add :user_id, :invalid unless watchable.possible_watcher?(user)
|
|
end
|
|
|
|
class << self
|
|
def prune_project_related(user_ids, projects)
|
|
watchers = watchers_in_projects(projects, user_ids)
|
|
|
|
watchers_by_watchable_class = watchers
|
|
.includes({ watchable: :project }, :user)
|
|
.group_by(&:watchable_type)
|
|
|
|
watchers_by_watchable_class.each do |watchable_class_string, class_candidates|
|
|
watchable_class = watchable_class_string.constantize
|
|
|
|
destroy_watchers_if_permission_missing(watchable_class, class_candidates)
|
|
end
|
|
end
|
|
|
|
def watchers_in_projects(projects, user_ids)
|
|
watchable_classes = active_watchable_classes(user_ids)
|
|
|
|
neutral_scope = where(Arel::Nodes::Equality.new(1, 0))
|
|
|
|
watchable_classes.inject(neutral_scope) do |aggregate_scope, watched_class|
|
|
klass = watched_class.constantize
|
|
|
|
individual_scope = watchers_of_watchable(klass, projects, user_ids)
|
|
|
|
aggregate_scope.or(individual_scope)
|
|
end
|
|
end
|
|
|
|
def destroy_watchers_if_permission_missing(watchable_class, class_candidates)
|
|
watchers_by_users = class_candidates.group_by(&:user)
|
|
watchers_by_projects = class_candidates.group_by { |c| c.watchable.project }
|
|
|
|
if watchers_by_users.keys.length < watchers_by_projects.keys.length
|
|
prune_by_users(watchers_by_users, watchable_class)
|
|
else
|
|
prune_by_projects(watchers_by_projects, watchable_class)
|
|
end
|
|
end
|
|
|
|
def prune_by_users(watchers_by_users, watchable_class)
|
|
watchers_by_users.each do |user, watchers|
|
|
allowed_project_ids = Project
|
|
.allowed_to(user,
|
|
watchable_class.acts_as_watchable_permission)
|
|
.pluck(:id)
|
|
watchers
|
|
.select { |w| !allowed_project_ids.include?(w.watchable.project.id) }
|
|
.each(&:destroy)
|
|
end
|
|
end
|
|
|
|
def prune_by_projects(watchers_by_projects, watchable_class)
|
|
watchers_by_projects.each do |project, watchers|
|
|
allowed_user_ids = User
|
|
.allowed(watchable_class.acts_as_watchable_permission,
|
|
project)
|
|
.pluck(:id)
|
|
|
|
watchers
|
|
.select { |c| !allowed_user_ids.include?(c.user_id) }
|
|
.each(&:destroy)
|
|
end
|
|
end
|
|
|
|
def watchers_of_watchable(watchable, projects, user_ids)
|
|
# By using
|
|
# where(projects: { id: project.id }
|
|
# instead of
|
|
# where(projects_id: project.id)
|
|
# we don't have to distinguish between project associations with
|
|
# project_id on the watchable class and those on a class associated to
|
|
# the watchable class (using :through).
|
|
id_subquery = watchable
|
|
.joins(:watchers)
|
|
.joins(:project)
|
|
.where(projects: { id: projects.map(&:id) })
|
|
.select('watchers.id')
|
|
|
|
id_subquery = id_subquery.where(watchers: { user_id: user_ids }) unless user_ids.empty?
|
|
|
|
where(id: id_subquery)
|
|
end
|
|
|
|
def active_watchable_classes(user_ids)
|
|
classes = distinct(:watchable_type)
|
|
classes.where(user_id: user_ids) unless user_ids.blank?
|
|
classes.pluck(:watchable_type)
|
|
end
|
|
end
|
|
end
|
|
|