Skip to content
Snippets Groups Projects
Select Git revision
  • 69906b74f7a27bf29dc967b6800e312207f1110f
  • eip-develop default
  • develop
  • 0valt/csrf
  • dependabot/bundler/bundler-6d4d941ed1
  • dependabot/npm_and_yarn/npm_and_yarn-a48ea45f8c
  • 0valt/caching
  • art/mod-spam-tools
  • 0valt/keyboard
  • 0valt/1808/unnecessary-ajax
  • 0valt/assorted
  • 0valt/general-fixes
  • 0valt/tour-fixes
  • 0valt/1815/tags-ordering
  • 0valt/voting-improvements
  • 0valt/1459/draft-discard
  • 0valt/1790/preferences
  • 0valt/query-optimizations
  • 0valt/1809/settings-wrap
  • 0valt/1805/sign-in-redirect-fix
  • 0valt/1804/kbd
  • 0valt/1292/flag-modal
  • v0.12.2
  • v0.12.1
  • v0.12.0
  • v0.11.0
  • v0.10.0
  • v0.9.0
  • v0.8.0
  • v0.7.0
  • v0.6.1
  • v0.6.0
  • v0.5.0
  • v0.4.0
  • v0.3.0
  • v1.0
36 results

community_user.rb

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    community_user.rb 5.89 KiB
    class CommunityUser < ApplicationRecord
      belongs_to :community
      belongs_to :user
    
      has_many :mod_warnings, dependent: :nullify
      has_many :user_abilities, dependent: :destroy
      belongs_to :deleted_by, required: false, class_name: 'User'
    
      validates :user_id, uniqueness: { scope: [:community_id], case_sensitive: false }
    
      scope :for_context, -> { where(community_id: RequestContext.community_id) }
      scope :active, -> { where(deleted: false) }
      scope :deleted, -> { where(deleted: true) }
    
      after_create :prevent_ulysses_case
    
      delegate :url_helpers, to: 'Rails.application.routes'
    
      def system?
        user_id == -1
      end
    
      def suspended?
        return true if is_suspended && !suspension_end.past?
    
        if is_suspended
          update(is_suspended: false, suspension_public_comment: nil, suspension_end: nil)
        end
    
        false
      end
    
      def latest_warning
        mod_warnings&.order(created_at: 'desc')&.first&.created_at
      end
    
      # Calculation functions for privilege scores
      # These are quite expensive, so we'll cache them for a while
      def post_score
        Rails.cache.fetch("privileges/#{id}/post_score", expires_in: 3.hours) do
          exclude_types = ApplicationController.helpers.post_type_ids(is_freely_editable: true)
          good_posts = Post.where(user: user).where('score > 0.5').where.not(post_type_id: exclude_types).count
          bad_posts = Post.where(user: user).where('score < 0.5').where.not(post_type_id: exclude_types).count
    
          (good_posts + 2.0) / (good_posts + bad_posts + 4.0)
        end
      end
    
      def edit_score
        Rails.cache.fetch("privileges/#{id}/edit_score", expires_in: 3.hours) do
          good_edits = SuggestedEdit.where(user: user).where(active: false, accepted: true).count
          bad_edits = SuggestedEdit.where(user: user).where(active: false, accepted: false).count
    
          (good_edits + 2.0) / (good_edits + bad_edits + 4.0)
        end
      end
    
      def flag_score
        Rails.cache.fetch("privileges/#{id}/flag_score", expires_in: 3.hours) do
          good_flags = Flag.where(user: user).where(status: 'helpful').count
          bad_flags = Flag.where(user: user).where(status: 'declined').count
    
          (good_flags + 2.0) / (good_flags + bad_flags + 4.0)
        end
      end
    
      def privilege?(internal_id, ignore_suspension: false, ignore_mod: false)
        if internal_id != 'mod' && !ignore_mod && user.is_moderator
          return true # includes: privilege? 'mod'
        end
    
        up = privilege(internal_id)
        if ignore_suspension
          !up.nil?
        else
          !up.nil? && !up.suspended?
        end
      end
    
      def privilege(internal_id)
        UserAbility.joins(:ability).where(community_user_id: id, abilities: { internal_id: internal_id }).first
      end
    
      ##
      # Grant a specified ability to this CommunityUser.
      # @param internal_id [String] The +internal_id+ of the ability to grant.
      # @param notify [Boolean] Whether to send a notification to the user.
      def grant_privilege!(internal_id, notify: true)
        priv = Ability.where(internal_id: internal_id).first
        UserAbility.create community_user_id: id, ability: priv
        if notify
          community_host = priv.community.host
          user.create_notification("You've earned the #{priv.name} ability! Learn more.",
                                   url_helpers.ability_url(priv.internal_id, host: community_host))
        end
      end
    
      ##
      # Recalculate a specified ability for this CommunityUser. Will not revoke abilities that have already been granted.
      # @param internal_id [String] The +internal_id+ of the ability to be recalculated.
      # @param sandbox [Boolean] Whether to run in sandbox mode - if sandboxed, the ability will not be granted but the
      #   return value indicates whether it would have been.
      # @return [Boolean] Whether or not the ability was granted.
      def recalc_privilege(internal_id, sandbox: false)
        # Do not recalculate privileges already granted
        return true if privilege?(internal_id, ignore_suspension: true, ignore_mod: false)
    
        priv = Ability.where(internal_id: internal_id).first
    
        # Do not recalculate privileges which are only manually given
        return false if priv.manual?
    
        # Grant :unrestricted automatically on new communities
        unless SiteSetting['NewSiteMode'] && internal_id.to_s == 'unrestricted'
          # Abort if any of the checks fails
          return false if !priv.post_score_threshold.nil? && post_score < priv.post_score_threshold
          return false if !priv.edit_score_threshold.nil? && edit_score < priv.edit_score_threshold
          return false if !priv.flag_score_threshold.nil? && flag_score < priv.flag_score_threshold
        end
    
        # If not sandbox mode, create new privilege entry
        grant_privilege!(internal_id) unless sandbox
        recalc_trust_level unless sandbox
        true
      end
    
      ##
      # Recalculate a list of standard abilities for this CommunityUser.
      # @param sandbox [Boolean] Whether to run in sandbox mode - see {#recalc_privilege}.
      # @return [Array<Boolean>]
      def recalc_privileges(sandbox: false)
        [:everyone, :unrestricted, :edit_posts, :edit_tags, :flag_close, :flag_curate].map do |ability|
          recalc_privilege(ability, sandbox: sandbox)
        end
      end
    
      alias ability? privilege?
      alias ability privilege
      alias grant_ability! grant_privilege!
      alias recalc_ability recalc_privilege
      alias recalc_abilities recalc_privileges
    
      # This check makes sure that every user gets the
      # 'everyone' permission upon creation. We do not want
      # to create a no permissions user by accident.
      # Polyphemus is very grateful for this.
      def prevent_ulysses_case
        recalc_privileges
      end
    
      def trust_level
        attributes['trust_level'] || recalc_trust_level
      end
    
      def recalc_trust_level
        trust = if user.staff?
                  5
                elsif is_moderator || user.is_global_moderator || is_admin || user.is_global_admin
                  4
                elsif privilege?('flag_close') || privilege?('edit_posts')
                  3
                elsif privilege?('unrestricted')
                  2
                else
                  1
                end
        update(trust_level: trust)
        trust
      end
    end