2009-01-08 16:33:27 +01:00
|
|
|
require 'digest/sha1'
|
2009-01-06 11:49:19 +01:00
|
|
|
# specific user rights through memberships (see Group)
|
2019-01-13 07:05:54 +01:00
|
|
|
class User < ApplicationRecord
|
2017-10-12 20:50:40 +02:00
|
|
|
include CustomFields
|
2021-03-01 15:27:26 +01:00
|
|
|
# TODO: acts_as_paraniod ??
|
2017-10-12 20:50:40 +02:00
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
has_many :memberships, :dependent => :destroy
|
|
|
|
has_many :groups, :through => :memberships
|
2021-03-01 15:27:26 +01:00
|
|
|
# has_one :ordergroup, :through => :memberships, :source => :group, :class_name => "Ordergroup"
|
2012-09-30 21:15:55 +02:00
|
|
|
def ordergroup
|
2021-03-01 15:27:26 +01:00
|
|
|
Ordergroup.joins(:memberships).where(memberships: { user_id: self.id }).first
|
2012-09-30 21:15:55 +02:00
|
|
|
end
|
|
|
|
|
2009-02-02 17:12:08 +01:00
|
|
|
has_many :workgroups, :through => :memberships, :source => :group, :class_name => "Workgroup"
|
2009-01-06 11:49:19 +01:00
|
|
|
has_many :assignments, :dependent => :destroy
|
|
|
|
has_many :tasks, :through => :assignments
|
2009-01-15 18:26:37 +01:00
|
|
|
has_many :send_messages, :class_name => "Message", :foreign_key => "sender_id"
|
2012-11-12 14:24:49 +01:00
|
|
|
has_many :created_orders, :class_name => 'Order', :foreign_key => 'created_by_user_id', :dependent => :nullify
|
2017-08-18 09:17:19 +02:00
|
|
|
has_many :mail_delivery_status, :class_name => 'MailDeliveryStatus', :foreign_key => 'email', :primary_key => 'email'
|
2019-01-13 07:05:54 +01:00
|
|
|
|
2020-03-06 11:46:39 +01:00
|
|
|
attr_accessor :create_ordergroup, :password, :send_welcome_mail, :settings_attributes
|
2016-03-04 19:03:52 +01:00
|
|
|
|
|
|
|
scope :deleted, -> { where.not(deleted_at: nil) }
|
|
|
|
scope :undeleted, -> { where(deleted_at: nil) }
|
|
|
|
|
2013-06-06 03:40:15 +02:00
|
|
|
# makes the current_user (logged-in-user) available in models
|
|
|
|
cattr_accessor :current_user
|
2019-01-13 07:05:54 +01:00
|
|
|
|
2013-09-20 22:39:19 +02:00
|
|
|
validates_presence_of :email
|
2011-06-10 13:22:15 +02:00
|
|
|
validates_presence_of :password, :on => :create
|
2014-02-20 15:04:53 +01:00
|
|
|
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
|
2010-02-16 09:53:24 +01:00
|
|
|
validates_uniqueness_of :email, :case_sensitive => false
|
2013-12-03 22:03:19 +01:00
|
|
|
validates_presence_of :first_name # for simple_form validations
|
2009-01-06 11:49:19 +01:00
|
|
|
validates_length_of :first_name, :in => 2..50
|
2009-01-12 18:26:09 +01:00
|
|
|
validates_confirmation_of :password
|
2022-02-18 11:20:09 +01:00
|
|
|
validates_length_of :password, :in => 5..50, :allow_blank => true
|
2013-11-18 11:31:30 +01:00
|
|
|
# allow nick to be nil depending on foodcoop config
|
|
|
|
# TODO Rails 4 may have a more beautiful way
|
|
|
|
# http://stackoverflow.com/questions/19845910/conditional-allow-nil-part-of-validation
|
|
|
|
validates_length_of :nick, :in => 2..25, :allow_nil => true, :unless => Proc.new { FoodsoftConfig[:use_nick] }
|
|
|
|
validates_length_of :nick, :in => 2..25, :allow_nil => false, :if => Proc.new { FoodsoftConfig[:use_nick] }
|
|
|
|
validates_uniqueness_of :nick, :case_sensitive => false, :allow_nil => true # allow_nil in length validation
|
2017-01-18 11:12:56 +01:00
|
|
|
validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, :allow_blank => true
|
|
|
|
validates_uniqueness_of :iban, :case_sensitive => false, :allow_blank => true
|
2009-01-12 18:26:09 +01:00
|
|
|
|
|
|
|
before_validation :set_password
|
2013-06-06 03:40:15 +02:00
|
|
|
after_initialize do
|
|
|
|
settings.defaults['profile'] = { 'language' => I18n.default_locale } unless settings.profile
|
|
|
|
settings.defaults['messages'] = { 'send_as_email' => true } unless settings.messages
|
2021-03-01 15:27:26 +01:00
|
|
|
settings.defaults['notify'] = { 'upcoming_tasks' => true } unless settings.notify
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2019-01-13 07:05:54 +01:00
|
|
|
|
2020-03-06 11:46:39 +01:00
|
|
|
before_save do
|
|
|
|
if send_welcome_mail?
|
|
|
|
self.reset_password_token = new_random_password(16)
|
|
|
|
self.reset_password_expires = Time.now.advance(days: 2)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-06 03:40:15 +02:00
|
|
|
after_save do
|
|
|
|
settings_attributes.each do |key, value|
|
|
|
|
value.each do |k, v|
|
|
|
|
case v
|
2021-03-01 15:27:26 +01:00
|
|
|
when '1'
|
|
|
|
value[k] = true
|
|
|
|
when '0'
|
|
|
|
value[k] = false
|
2013-06-06 03:40:15 +02:00
|
|
|
end
|
2009-01-13 19:01:56 +01:00
|
|
|
end
|
2013-06-06 03:40:15 +02:00
|
|
|
self.settings.merge!(key, value)
|
2014-11-21 14:37:56 +01:00
|
|
|
end if settings_attributes
|
2019-11-17 15:01:19 +01:00
|
|
|
|
2020-09-04 12:57:19 +02:00
|
|
|
if ActiveModel::Type::Boolean.new.cast(create_ordergroup)
|
2021-03-01 15:27:26 +01:00
|
|
|
og = Ordergroup.new({ name: name })
|
|
|
|
og.memberships.build({ user: self })
|
2019-11-17 15:01:19 +01:00
|
|
|
og.save!
|
|
|
|
end
|
2020-03-06 11:46:39 +01:00
|
|
|
|
|
|
|
Mailer.welcome(self).deliver_now if send_welcome_mail?
|
|
|
|
end
|
|
|
|
|
|
|
|
def send_welcome_mail?
|
2020-09-04 12:57:19 +02:00
|
|
|
ActiveModel::Type::Boolean.new.cast(send_welcome_mail)
|
2009-01-12 18:26:09 +01:00
|
|
|
end
|
2013-09-20 23:18:06 +02:00
|
|
|
|
|
|
|
# sorted by display name
|
|
|
|
def self.natural_order
|
|
|
|
# would be sensible to match ApplicationController#show_user
|
|
|
|
if FoodsoftConfig[:use_nick]
|
2014-05-06 10:51:45 +02:00
|
|
|
order('nick')
|
2013-09-20 23:18:06 +02:00
|
|
|
else
|
2014-05-06 10:51:45 +02:00
|
|
|
order('first_name', 'last_name')
|
2013-09-20 23:18:06 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# search by (nick)name
|
|
|
|
def self.natural_search(q)
|
2013-12-22 23:21:02 +01:00
|
|
|
q = q.strip
|
|
|
|
users = User.arel_table
|
|
|
|
# full string as nickname
|
|
|
|
match_nick = users[:nick].matches("%#{q}%")
|
|
|
|
# or each word matches either first or last name
|
|
|
|
match_name = q.split.map do |a|
|
2021-03-01 15:27:26 +01:00
|
|
|
users[:first_name].matches("%#{a}%").or users[:last_name].matches("%#{a}%")
|
2013-12-22 23:21:02 +01:00
|
|
|
end.reduce(:and)
|
|
|
|
User.where(match_nick.or match_name)
|
2013-09-20 23:18:06 +02:00
|
|
|
end
|
2019-01-13 07:05:54 +01:00
|
|
|
|
2013-06-14 02:39:25 +02:00
|
|
|
def locale
|
|
|
|
settings.profile['language']
|
|
|
|
end
|
2019-01-13 07:05:54 +01:00
|
|
|
|
2009-02-02 16:35:43 +01:00
|
|
|
def name
|
|
|
|
[first_name, last_name].join(" ")
|
|
|
|
end
|
|
|
|
|
2012-08-24 11:11:40 +02:00
|
|
|
def receive_email?
|
2013-09-06 11:40:10 +02:00
|
|
|
settings.messages['send_as_email'] && email.present?
|
2012-08-24 11:11:40 +02:00
|
|
|
end
|
2019-01-13 07:05:54 +01:00
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Sets the user's password. It will be stored encrypted along with a random salt.
|
2009-01-12 18:26:09 +01:00
|
|
|
def set_password
|
|
|
|
unless password.blank?
|
2021-03-01 15:27:26 +01:00
|
|
|
salt = [Array.new(6) { rand(256).chr }.join].pack("m").chomp
|
2009-01-12 18:26:09 +01:00
|
|
|
self.password_hash, self.password_salt = Digest::SHA1.hexdigest(password + salt), salt
|
|
|
|
end
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2019-01-13 07:05:54 +01:00
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Returns true if the password argument matches the user's password.
|
|
|
|
def has_password(password)
|
|
|
|
Digest::SHA1.hexdigest(password + self.password_salt) == self.password_hash
|
|
|
|
end
|
2019-01-13 07:05:54 +01:00
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Returns a random password.
|
2022-02-16 18:13:08 +01:00
|
|
|
def new_random_password(size = 6)
|
2009-01-06 11:49:19 +01:00
|
|
|
c = %w(b c d f g h j k l m n p qu r s t v w x z ch cr fr nd ng nk nt ph pr rd sh sl sp st th tr)
|
|
|
|
v = %w(a e i o u y)
|
|
|
|
f, r = true, ''
|
|
|
|
(size * 2).times do
|
|
|
|
r << (f ? c[rand * c.size] : v[rand * v.size])
|
|
|
|
f = !f
|
|
|
|
end
|
|
|
|
r
|
|
|
|
end
|
2015-04-24 15:19:57 +02:00
|
|
|
|
|
|
|
# Generates password reset token and sends email
|
|
|
|
# @return [Boolean] Whether it succeeded or not
|
|
|
|
def request_password_reset!
|
|
|
|
self.reset_password_token = new_random_password(16)
|
|
|
|
self.reset_password_expires = Time.now.advance(days: 2)
|
|
|
|
if save!
|
|
|
|
Mailer.reset_password(self).deliver_now
|
|
|
|
logger.debug("Sent password reset email to #{email}.")
|
|
|
|
true
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Checks the admin role
|
|
|
|
def role_admin?
|
2021-03-01 15:27:26 +01:00
|
|
|
groups.detect { |group| group.role_admin? }
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2019-01-13 07:05:54 +01:00
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Checks the finance role
|
|
|
|
def role_finance?
|
2021-03-01 15:27:26 +01:00
|
|
|
FoodsoftConfig[:default_role_finance] || groups.detect { |group| group.role_finance? }
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2016-02-17 21:07:35 +01:00
|
|
|
|
|
|
|
# Checks the invoices role
|
|
|
|
def role_invoices?
|
2021-03-01 15:27:26 +01:00
|
|
|
FoodsoftConfig[:default_role_invoices] || groups.detect { |group| group.role_invoices? }
|
2016-02-17 21:07:35 +01:00
|
|
|
end
|
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Checks the article_meta role
|
|
|
|
def role_article_meta?
|
2021-03-01 15:27:26 +01:00
|
|
|
FoodsoftConfig[:default_role_article_meta] || groups.detect { |group| group.role_article_meta? }
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2017-11-15 23:58:11 +01:00
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Checks the suppliers role
|
|
|
|
def role_suppliers?
|
2021-03-01 15:27:26 +01:00
|
|
|
FoodsoftConfig[:default_role_suppliers] || groups.detect { |group| group.role_suppliers? }
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2017-11-15 23:58:11 +01:00
|
|
|
|
|
|
|
# Checks the invoices role
|
|
|
|
def role_pickups?
|
2021-03-01 15:27:26 +01:00
|
|
|
FoodsoftConfig[:default_role_pickups] || groups.detect { |group| group.role_pickups? }
|
2017-11-15 23:58:11 +01:00
|
|
|
end
|
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Checks the orders role
|
|
|
|
def role_orders?
|
2021-03-01 15:27:26 +01:00
|
|
|
FoodsoftConfig[:default_role_orders] || groups.detect { |group| group.role_orders? }
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2017-11-15 23:58:11 +01:00
|
|
|
|
2009-01-29 21:28:22 +01:00
|
|
|
def ordergroup_name
|
2013-03-22 01:21:44 +01:00
|
|
|
ordergroup ? ordergroup.name : I18n.t('model.user.no_ordergroup')
|
2009-01-29 21:28:22 +01:00
|
|
|
end
|
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# returns true if user is a member of a given group
|
2009-02-06 20:51:14 +01:00
|
|
|
def member_of?(group)
|
|
|
|
group.users.exists?(self.id)
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2017-11-15 23:58:11 +01:00
|
|
|
|
2021-03-01 15:27:26 +01:00
|
|
|
# Returns an array with the users groups (but without the Ordergroups -> because tpye=>"")
|
2009-01-06 11:49:19 +01:00
|
|
|
def member_of_groups()
|
2021-03-01 15:27:26 +01:00
|
|
|
self.groups.where(type: '')
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
|
|
|
|
2016-03-04 19:03:52 +01:00
|
|
|
def deleted?
|
|
|
|
deleted_at.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def mark_as_deleted
|
|
|
|
update_column :deleted_at, Time.now
|
|
|
|
end
|
|
|
|
|
|
|
|
def restore
|
|
|
|
update_column :deleted_at, nil
|
|
|
|
end
|
|
|
|
|
2013-11-11 16:23:20 +01:00
|
|
|
def self.authenticate(login, password)
|
2015-01-14 21:15:08 +01:00
|
|
|
user = find_by_nick(login) || find_by_email(login)
|
2019-11-11 11:07:52 +01:00
|
|
|
if user && password && user.has_password(password)
|
2011-05-11 13:38:46 +02:00
|
|
|
user
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-05 22:16:32 +01:00
|
|
|
def self.custom_fields
|
|
|
|
fields = FoodsoftConfig[:custom_fields] && FoodsoftConfig[:custom_fields][:user]
|
|
|
|
return [] unless fields
|
2021-03-01 15:27:26 +01:00
|
|
|
|
2017-11-05 22:16:32 +01:00
|
|
|
fields.map(&:deep_symbolize_keys)
|
|
|
|
end
|
|
|
|
|
2013-09-22 14:38:56 +02:00
|
|
|
# XXX this is view-related; need to move out things like token_attributes
|
|
|
|
# then this can be removed
|
|
|
|
def display
|
2013-10-29 18:58:04 +01:00
|
|
|
# would be sensible to match ApplicationHelper#show_user
|
|
|
|
if FoodsoftConfig[:use_nick]
|
|
|
|
nick.nil? ? I18n.t('helpers.application.nick_fallback') : nick
|
|
|
|
else
|
|
|
|
name
|
|
|
|
end
|
2013-09-22 14:38:56 +02:00
|
|
|
end
|
|
|
|
|
2011-05-15 22:06:54 +02:00
|
|
|
def token_attributes
|
2013-09-22 14:38:56 +02:00
|
|
|
# would be sensible to match ApplicationController#show_user
|
|
|
|
# this should not be part of the model anyway
|
2021-03-01 15:27:26 +01:00
|
|
|
{ :id => id, :name => "#{display} (#{ordergroup.try(:name)})" }
|
2011-05-15 22:06:54 +02:00
|
|
|
end
|
2022-05-27 17:06:25 +02:00
|
|
|
|
|
|
|
def self.sort_by_param(param)
|
|
|
|
param ||= "name"
|
|
|
|
|
|
|
|
sort_param_map = {
|
|
|
|
"nick" => "nick",
|
|
|
|
"nick_reverse" => "nick DESC",
|
|
|
|
"name" => "first_name, last_name",
|
|
|
|
"name_reverse" => "first_name DESC, last_name DESC",
|
|
|
|
"email" => "users.email",
|
|
|
|
"email_reverse" => "users.email DESC",
|
|
|
|
"phone" => "phone",
|
|
|
|
"phone_reverse" => "phone DESC",
|
|
|
|
"last_activity" => "last_activity",
|
|
|
|
"last_activity_reverse" => "last_activity DESC",
|
|
|
|
"ordergroup" => "IFNULL(groups.type, '') <> 'Ordergroup', groups.name",
|
|
|
|
"ordergroup_reverse" => "IFNULL(groups.type, '') <> 'Ordergroup', groups.name DESC"
|
|
|
|
}
|
|
|
|
|
|
|
|
# Never pass user input data to Arel.sql() because of SQL Injection vulnerabilities.
|
|
|
|
# This case here is okay, as param is mapped to the actual order string.
|
|
|
|
self.eager_load(:groups).order(Arel.sql(sort_param_map[param])) # eager_load is like left_join but without duplicates
|
|
|
|
end
|
2009-01-08 16:33:27 +01:00
|
|
|
end
|