<commit_before>#   Copyright (c) 2010, Diaspora Inc.  This file is
#   licensed under the Affero General Public License version 3 or later.  See
#   the COPYRIGHT file.

require 'uri'
require File.join(Rails.root, 'lib/hcard')

class Person < ActiveRecord::Base
  include ROXML
  include Encryptor::Public
  require File.join(Rails.root, 'lib/diaspora/web_socket')
  include Diaspora::Socketable
  include Diaspora::Guid

  xml_attr :diaspora_handle
  xml_attr :url
  xml_attr :profile, :as => Profile
  xml_attr :exported_key

  has_one :profile, :dependent => :destroy
  delegate :last_name, :to => :profile
  accepts_nested_attributes_for :profile

  before_validation :downcase_diaspora_handle
  def downcase_diaspora_handle
    diaspora_handle.downcase! unless diaspora_handle.blank?
  end

  has_many :contacts, :dependent => :destroy # Other people's contacts for this person
  has_many :posts, :foreign_key => :author_id, :dependent => :destroy # This person's own posts
  has_many :comments, :foreign_key => :author_id, :dependent => :destroy # This person's own comments

  belongs_to :owner, :class_name => 'User'

  has_many :notification_actors
  has_many :notifications, :through => :notification_actors

  has_many :mentions, :dependent => :destroy

  before_destroy :remove_all_traces
  before_validation :clean_url

  validates_presence_of :url, :profile, :serialized_public_key
  validates_uniqueness_of :diaspora_handle

  scope :searchable, joins(:profile).where(:profiles => {:searchable => true})
  scope :remote, where('people.owner_id IS NULL')
  scope :local, where('people.owner_id IS NOT NULL')
  scope :for_json, select('DISTINCT people.id, people.diaspora_handle').includes(:profile)

  def self.featured_users
    AppConfig[:featured_users].present? ? Person.where(:diaspora_handle => AppConfig[:featured_users]) : []
  end

  # Set a default of an empty profile when a new Person record is instantiated.
  # Passing :profile => nil to Person.new will instantiate a person with no profile.
  # Calling Person.new with a block:
  #   Person.new do |p|
  #     p.profile = nil
  #   end
  # will not work!  The nil profile will be overriden with an empty one.
  def initialize(params={})
    profile_set = params.has_key?(:profile) || params.has_key?("profile")
    params[:profile_attributes] = params.delete(:profile) if params.has_key?(:profile) && params[:profile].is_a?(Hash)
    super
    self.profile ||= Profile.new unless profile_set
  end
  
  
  def self.find_from_id_or_username(params)
    p =   if params[:id].present?
            Person.where(:id => params[:id]).first
          elsif params[:username].present? && u = User.find_by_username(params[:username])
            u.person
          else
            nil
          end
    raise ActiveRecord::RecordNotFound unless p.present?
    p
  end


  def self.search_query_string(query)
    query = query.downcase
    like_operator = postgres? ? "ILIKE" : "LIKE"

    where_clause = <<-SQL
      profiles.full_name #{like_operator} ? OR
      people.diaspora_handle #{like_operator} ?
    SQL

    q_tokens = []
    q_tokens[0] = query.to_s.strip.gsub(/(\s|$|^)/) { "%#{$1}" }
    q_tokens[1] = q_tokens[0].gsub(/\s/,'').gsub('%','')
    q_tokens[1] << "%"

    [where_clause, q_tokens]
  end

  def self.search(query, user)
    return self.where("1 = 0") if query.to_s.blank? || query.to_s.length < 2

    sql, tokens = self.search_query_string(query)
    pp sql,tokens

    Person.searchable.where(sql, *tokens).joins(
      "LEFT OUTER JOIN contacts ON contacts.user_id = #{user.id} AND contacts.person_id = people.id"
    ).includes(:profile
    ).order(search_order)
  end

  # @return [Array<String>] postgreSQL and mysql deal with null values in orders differently, it seems.
  def self.search_order
    @search_order ||= Proc.new {
      order = if postgres?
        "ASC"
      else
        "DESC"
      end
      ["contacts.user_id #{order}", "profiles.last_name ASC", "profiles.first_name ASC"]
    }.call<commit_msg>removed a puts in Person.search<commit_after>#   Copyright (c) 2010, Diaspora Inc.  This file is
#   licensed under the Affero General Public License version 3 or later.  See
#   the COPYRIGHT file.

require 'uri'
require File.join(Rails.root, 'lib/hcard')

class Person < ActiveRecord::Base
  include ROXML
  include Encryptor::Public
  require File.join(Rails.root, 'lib/diaspora/web_socket')
  include Diaspora::Socketable
  include Diaspora::Guid

  xml_attr :diaspora_handle
  xml_attr :url
  xml_attr :profile, :as => Profile
  xml_attr :exported_key

  has_one :profile, :dependent => :destroy
  delegate :last_name, :to => :profile
  accepts_nested_attributes_for :profile

  before_validation :downcase_diaspora_handle
  def downcase_diaspora_handle
    diaspora_handle.downcase! unless diaspora_handle.blank?
  end

  has_many :contacts, :dependent => :destroy # Other people's contacts for this person
  has_many :posts, :foreign_key => :author_id, :dependent => :destroy # This person's own posts
  has_many :comments, :foreign_key => :author_id, :dependent => :destroy # This person's own comments

  belongs_to :owner, :class_name => 'User'

  has_many :notification_actors
  has_many :notifications, :through => :notification_actors

  has_many :mentions, :dependent => :destroy

  before_destroy :remove_all_traces
  before_validation :clean_url

  validates_presence_of :url, :profile, :serialized_public_key
  validates_uniqueness_of :diaspora_handle

  scope :searchable, joins(:profile).where(:profiles => {:searchable => true})
  scope :remote, where('people.owner_id IS NULL')
  scope :local, where('people.owner_id IS NOT NULL')
  scope :for_json, select('DISTINCT people.id, people.diaspora_handle').includes(:profile)

  def self.featured_users
    AppConfig[:featured_users].present? ? Person.where(:diaspora_handle => AppConfig[:featured_users]) : []
  end

  # Set a default of an empty profile when a new Person record is instantiated.
  # Passing :profile => nil to Person.new will instantiate a person with no profile.
  # Calling Person.new with a block:
  #   Person.new do |p|
  #     p.profile = nil
  #   end
  # will not work!  The nil profile will be overriden with an empty one.
  def initialize(params={})
    profile_set = params.has_key?(:profile) || params.has_key?("profile")
    params[:profile_attributes] = params.delete(:profile) if params.has_key?(:profile) && params[:profile].is_a?(Hash)
    super
    self.profile ||= Profile.new unless profile_set
  end
  
  
  def self.find_from_id_or_username(params)
    p =   if params[:id].present?
            Person.where(:id => params[:id]).first
          elsif params[:username].present? && u = User.find_by_username(params[:username])
            u.person
          else
            nil
          end
    raise ActiveRecord::RecordNotFound unless p.present?
    p
  end


  def self.search_query_string(query)
    query = query.downcase
    like_operator = postgres? ? "ILIKE" : "LIKE"

    where_clause = <<-SQL
      profiles.full_name #{like_operator} ? OR
      people.diaspora_handle #{like_operator} ?
    SQL

    q_tokens = []
    q_tokens[0] = query.to_s.strip.gsub(/(\s|$|^)/) { "%#{$1}" }
    q_tokens[1] = q_tokens[0].gsub(/\s/,'').gsub('%','')
    q_tokens[1] << "%"

    [where_clause, q_tokens]
  end

  def self.search(query, user)
    return self.where("1 = 0") if query.to_s.blank? || query.to_s.length < 2

    sql, tokens = self.search_query_string(query)

    Person.searchable.where(sql, *tokens).joins(
      "LEFT OUTER JOIN contacts ON contacts.user_id = #{user.id} AND contacts.person_id = people.id"
    ).includes(:profile
    ).order(search_order)
  end

  # @return [Array<String>] postgreSQL and mysql deal with null values in orders differently, it seems.
  def self.search_order
    @search_order ||= Proc.new {
      order = if postgres?
        "ASC"
      else
        "DESC"
      end
      ["contacts.user_id #{order}", "profiles.last_name ASC", "profiles.first_name ASC"]
    }.call
{'fraction_non_alphanumeric': 0.1181886041303807, 'fraction_numerical': 0.0032346354814630504, 'mean_word_length': 3.7399764150943398, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '767878', 'n_tokens_mistral': 2693, 'n_tokens_neox': 2583, 'n_words': 655}