+ def description
+ RichText.new(self[:description_format], self[:description])
+ end
+
+ def languages
+ attribute_present?(:languages) ? self[:languages].split(/ *[, ] */) : []
+ end
+
+ def languages=(languages)
+ self[:languages] = languages.join(",")
+ end
+
+ def preferred_language
+ languages.find { |l| Language.exists?(:code => l) }
+ end
+
+ def preferred_languages
+ @preferred_languages ||= Locale.list(languages)
+ end
+
+ def reset_preferred_languages
+ @preferred_languages = nil
+ end
+
+ def nearby(radius = Settings.nearby_radius, num = Settings.nearby_users)
+ if home_lon && home_lat
+ gc = OSM::GreatCircle.new(home_lat, home_lon)
+ sql_for_area = QuadTile.sql_for_area(gc.bounds(radius), "home_")
+ sql_for_distance = gc.sql_for_distance("home_lat", "home_lon")
+ nearby = User.active.identifiable
+ .where.not(:id => id)
+ .where(sql_for_area)
+ .where("#{sql_for_distance} <= ?", radius)
+ .order(Arel.sql(sql_for_distance))
+ .limit(num)
+ else
+ nearby = []
+ end
+ nearby
+ end
+
+ def distance(nearby_user)
+ OSM::GreatCircle.new(home_lat, home_lon).distance(nearby_user.home_lat, nearby_user.home_lon)
+ end
+
+ def is_friends_with?(new_friend)
+ friendships.exists?(:befriendee => new_friend)
+ end
+
+ ##
+ # returns true if a user is visible
+ def visible?
+ %w[pending active confirmed].include? status
+ end
+
+ ##
+ # returns true if a user is active
+ def active?
+ %w[active confirmed].include? status
+ end
+
+ ##
+ # returns true if the user has the moderator role, false otherwise
+ def moderator?
+ has_role? "moderator"
+ end
+
+ ##
+ # returns true if the user has the administrator role, false otherwise
+ def administrator?
+ has_role? "administrator"
+ end
+
+ ##
+ # returns true if the user has the requested role
+ def has_role?(role)
+ roles.any? { |r| r.role == role }
+ end
+
+ ##
+ # returns the first active block which would require users to view
+ # a message, or nil if there are none.
+ def blocked_on_view
+ blocks.active.detect(&:needs_view?)
+ end
+
+ ##
+ # delete a user - leave the account but purge most personal data
+ def delete
+ avatar.purge_later
+
+ self.display_name = "user_#{id}"
+ self.description = ""
+ self.home_lat = nil
+ self.home_lon = nil
+ self.email_valid = false
+ self.new_email = nil
+ self.auth_provider = nil
+ self.auth_uid = nil
+ self.status = "deleted"
+
+ save
+ end
+
+ ##
+ # return a spam score for a user
+ def spam_score
+ changeset_score = changesets.size * 50
+ trace_score = traces.size * 50
+ diary_entry_score = diary_entries.visible.inject(0) { |acc, elem| acc + elem.body.spam_score }
+ diary_comment_score = diary_comments.visible.inject(0) { |acc, elem| acc + elem.body.spam_score }
+
+ score = description.spam_score / 4.0
+ score += diary_entries.where("created_at > ?", 1.day.ago).count * 10
+ score += diary_entry_score / diary_entries.length unless diary_entries.empty?
+ score += diary_comment_score / diary_comments.length unless diary_comments.empty?
+ score -= changeset_score
+ score -= trace_score
+
+ score.to_i
+ end
+
+ ##
+ # perform a spam check on a user
+ def spam_check
+ update(:status => "suspended") if status == "active" && spam_score > Settings.spam_threshold
+ end
+
+ ##
+ # return an oauth access token for a specified application
+ def access_token(application_key)
+ ClientApplication.find_by(:key => application_key).access_token_for_user(self)
+ end
+
+ def fingerprint
+ digest = Digest::SHA256.new
+ digest.update(email)
+ digest.update(pass_crypt)
+ digest.hexdigest
+ end
+
+ private
+
+ def set_defaults
+ self.creation_time = Time.now.getutc unless attribute_present?(:creation_time)
+ end
+
+ def encrypt_password
+ if pass_crypt_confirmation
+ self.pass_crypt, self.pass_salt = PasswordHash.create(pass_crypt)
+ self.pass_crypt_confirmation = nil
+ end
+ end
+
+ def update_tile
+ self.home_tile = QuadTile.tile_for_point(home_lat, home_lon) if home_lat && home_lon
+ end