# frozen_string_literal: true module Decidim # This query finds the public ActionLog entries that can be shown in the # participant views of the application within a Decidim Organization. It is # intended to be used in the user profile, to retrieve activity and timeline # for that user. # # This will create the base query for the activities but the resulting query # needs to be scoped with either `with_resource_type` or # `with_new_resource_type` in order to avoid leaking private data. # # Possible options: # # :resource_name - an optional name for a resource for searching only that # type of resources. # :user - an optional `Decidim::User` that performed the activites # :current_user - an optional `Decidim::User` for defining whether to show the # private log entries that relate to the current user. # :follows - a collection of `Decidim::Follow` resources. It will return any # activity affecting any of these resources, performed by any of them or # contained in any of them as spaces. # :scopes - a collection of `Decidim::Scope`. It will return any activity that # took place in any of those scopes. class PublicActivities < Decidim::Query def initialize(organization, options = {}) @organization = organization @resource_name = options[:resource_name] @user = options[:user] @current_user = options[:current_user] @follows = options[:follows] @scopes = options[:scopes] end def query query = ActionLog .where(visibility: visibility) .where(organization: organization) query = query.where(user: user) if user query = query.where(resource_type: resource_name) if resource_name.present? query = filter_follows(query) query = filter_hidden(query) query.order(created_at: :desc) end private attr_reader :organization, :resource_name, :user, :current_user, :follows, :scopes def visibility %w(public-only all) end def filter_follows(query) conditions = [] if follows.present? conditions += follows.group_by(&:decidim_followable_type).map do |type, grouped_follows| Decidim::ActionLog.arel_table[:resource_type].eq(type).and( Decidim::ActionLog.arel_table[:resource_id].in(grouped_follows.map(&:decidim_followable_id)) ) end conditions += followed_users_conditions(follows) conditions += followed_spaces_conditions(follows) end conditions += interesting_scopes_conditions(scopes) return query if conditions.empty? chained_conditions = conditions.inject do |previous_condition, condition| previous_condition.present? ? previous_condition.or(condition) : condition end query.where(chained_conditions) end def filter_hidden(query) # Filter out the items that have been moderated. query = query.joins( <<~SQL.squish LEFT JOIN decidim_moderations ON decidim_moderations.decidim_reportable_type = decidim_action_logs.resource_type AND decidim_moderations.decidim_reportable_id = decidim_action_logs.resource_id AND decidim_moderations.hidden_at IS NOT NULL SQL ).where(decidim_moderations: { id: nil }) # Filter out the private space items that should not be visible for the # current user. current_user_id = current_user&.id.to_i Decidim.participatory_space_manifests.map do |manifest| klass = manifest.model_class_name.constantize next unless klass.include?(Decidim::HasPrivateUsers) table = klass.table_name query = query.joins( Arel.sql( <<~SQL.squish LEFT JOIN #{table} ON decidim_action_logs.participatory_space_type = '#{manifest.model_class_name}' AND #{table}.id = decidim_action_logs.participatory_space_id LEFT JOIN decidim_participatory_space_private_users AS #{manifest.name}_private_users ON #{manifest.name}_private_users.privatable_to_type = '#{manifest.model_class_name}' AND #{table}.id = #{manifest.name}_private_users.privatable_to_id AND #{table}.private_space = 't' SQL ).to_s ).where( Arel.sql( <<~SQL.squish #{table}.id IS NULL OR #{table}.private_space = 'f' OR #{manifest.name}_private_users.decidim_user_id = #{current_user_id} SQL ).to_s ) end query end def followed_users_conditions(follows) followed_users = follows.where(decidim_followable_type: "Decidim::UserBaseEntity") return [] if followed_users.empty? [Decidim::ActionLog.arel_table[:decidim_user_id].in(followed_users.map(&:decidim_followable_id))] end def followed_spaces_conditions(follows) followed_spaces = follows.where(decidim_followable_type: participatory_space_classes) return [] if followed_spaces.empty? followed_spaces.group_by(&:decidim_followable_type).map do |type, grouped_follows| Decidim::ActionLog.arel_table[:participatory_space_type].eq(type).and( Decidim::ActionLog.arel_table[:participatory_space_id].in(grouped_follows.map(&:decidim_followable_id)) ) end end def interesting_scopes_conditions(interesting_scopes) return [] if interesting_scopes.blank? [Decidim::ActionLog.arel_table[:decidim_scope_id].in(interesting_scopes.map(&:id))] end def participatory_space_classes Decidim.participatory_space_manifests.map(&:model_class_name) end end end