123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- class Avatar < ApplicationModel
- include HasDefaultModelUserRelations
- include Avatar::TriggersSubscriptions
- belongs_to :object_lookup, optional: true
- =begin
- add an avatar based on auto detection (email address)
- Avatar.auto_detection(
- object: 'User',
- o_id: user.id,
- url: 'somebody@example.com',
- updated_by_id: 1,
- created_by_id: 1,
- )
- =end
- def self.auto_detection(data)
- # return if we run import mode
- return if Setting.get('import_mode')
- return if data[:url].blank?
- Avatar.add(
- object: data[:object],
- o_id: data[:o_id],
- url: data[:url],
- source: 'zammad.com',
- deletable: false,
- updated_by_id: 1,
- created_by_id: 1,
- )
- end
- =begin
- add avatar by upload
- Avatar.add(
- object: 'User',
- o_id: user.id,
- default: true,
- full: {
- content: '...',
- mime_type: 'image/png',
- },
- resize: {
- content: '...',
- mime_type: 'image/png',
- },
- source: 'web',
- deletable: true,
- updated_by_id: 1,
- created_by_id: 1,
- )
- add avatar by url
- Avatar.add(
- object: 'User',
- o_id: user.id,
- default: true,
- url: ...,
- source: 'web',
- deletable: true,
- updated_by_id: 1,
- created_by_id: 1,
- )
- =end
- def self.add(data)
- # lookups
- if data[:object]
- object_id = ObjectLookup.by_name(data[:object])
- end
- # add initial avatar
- _add_init_avatar(object_id, data[:o_id])
- record = {
- o_id: data[:o_id],
- object_lookup_id: object_id,
- default: true,
- deletable: data[:deletable],
- initial: false,
- source: data[:source],
- source_url: data[:url],
- updated_by_id: data[:updated_by_id],
- created_by_id: data[:created_by_id],
- }
- # check if avatar with url already exists
- avatar_already_exists = nil
- if data[:source].present?
- avatar_already_exists = Avatar.find_by(
- object_lookup_id: object_id,
- o_id: data[:o_id],
- source: data[:source],
- )
- end
- # fetch image based on http url
- if data[:url].present?
- if data[:url].instance_of?(Tempfile)
- logger.info "Reading image from tempfile '#{data[:url].inspect}'"
- content = data[:url].read
- filename = data[:url].path
- mime_type = 'image'
- if filename.match?(%r{\.png}i)
- mime_type = 'image/png'
- end
- if filename.match?(%r{\.(jpg|jpeg)}i)
- mime_type = 'image/jpeg'
- end
- # forbid creation of avatars without a specified mime_type (image is not displayed in the UI)
- if mime_type == 'image'
- logger.info "Could not determine mime_type for image '#{data[:url].inspect}'"
- return
- end
- data[:resize] ||= {}
- data[:resize][:content] = content
- data[:resize][:mime_type] = mime_type
- data[:full] ||= {}
- data[:full][:content] = content
- data[:full][:mime_type] = mime_type
- elsif data[:url].to_s.match?(%r{^https?://})
- url = data[:url].to_s
- # check if source was updated within last 2 minutes
- return if avatar_already_exists&.source_url == url && avatar_already_exists.updated_at > 2.minutes.ago
- # twitter workaround to get bigger avatar images
- # see also https://dev.twitter.com/overview/general/user-profile-images-and-banners
- if url.match?(%r{//pbs.twimg.com/}i)
- url.sub!(%r{normal\.(png|jpg|gif)$}, 'bigger.\1')
- end
- # fetch image
- response = UserAgent.get(
- url,
- {},
- {
- open_timeout: 4,
- read_timeout: 6,
- total_timeout: 6,
- },
- )
- if !response.success?
- logger.info "Can't fetch '#{url}' (maybe no avatar available), http code: #{response.code}"
- return
- end
- logger.info "Fetched image '#{url}', http code: #{response.code}"
- mime_type = 'image'
- if url.match?(%r{\.png}i)
- mime_type = 'image/png'
- end
- if url.match?(%r{\.(jpg|jpeg)}i)
- mime_type = 'image/jpeg'
- end
- # fallback to content-type of the response if url does not end with png, jpg or jpeg
- # see https://github.com/zammad/zammad/issues/3829
- if mime_type == 'image' &&
- response.header['content-type'].present? &&
- Rails.application.config.active_storage.web_image_content_types.include?(response.header['content-type'])
- mime_type = response.header['content-type']
- end
- # forbid creation of avatars without a specified mime_type (image is not displayed in the UI)
- if mime_type == 'image'
- logger.info "Could not determine mime_type for image '#{url}'"
- return
- end
- data[:resize] ||= {}
- data[:resize][:content] = response.body
- data[:resize][:mime_type] = mime_type
- data[:full] ||= {}
- data[:full][:content] = response.body
- data[:full][:mime_type] = mime_type
- # try zammad backend to find image based on email
- elsif data[:url].to_s.match?(URI::MailTo::EMAIL_REGEXP)
- url = data[:url].to_s
- # check if source ist already updated within last 3 minutes
- return if avatar_already_exists&.source_url == url && avatar_already_exists.updated_at > 2.minutes.ago
- # fetch image
- image = Service::Image.user(url)
- return if !image
- data[:resize] = image
- data[:full] = image
- end
- end
- # check if avatar needs to be updated
- if data[:resize].present? && data[:resize][:content].present?
- record[:store_hash] = Digest::MD5.hexdigest(data[:resize][:content])
- if avatar_already_exists&.store_hash == record[:store_hash]
- avatar_already_exists.touch # rubocop:disable Rails/SkipsModelValidations
- return avatar_already_exists
- end
- end
- # store images
- object_name = "Avatar::#{data[:object]}"
- if data[:full].present?
- store_full = Store.create!(
- object: "#{object_name}::Full",
- o_id: data[:o_id],
- data: data[:full][:content],
- filename: 'avatar_full',
- preferences: {
- 'Mime-Type' => data[:full][:mime_type]
- },
- created_by_id: data[:created_by_id],
- )
- record[:store_full_id] = store_full.id
- record[:store_hash] = Digest::MD5.hexdigest(data[:full][:content])
- end
- if data[:resize].present?
- store_resize = Store.create!(
- object: "#{object_name}::Resize",
- o_id: data[:o_id],
- data: data[:resize][:content],
- filename: 'avatar',
- preferences: {
- 'Mime-Type' => data[:resize][:mime_type]
- },
- created_by_id: data[:created_by_id],
- )
- record[:store_resize_id] = store_resize.id
- record[:store_hash] = Digest::MD5.hexdigest(data[:resize][:content])
- end
- return if record[:store_resize_id].blank? || record[:store_hash].blank?
- # update existing
- if avatar_already_exists
- avatar_already_exists.update!(record)
- avatar = avatar_already_exists
- # add new one and set it as default
- else
- avatar = Avatar.create(record)
- set_default_items(object_id, data[:o_id], avatar.id)
- end
- avatar
- end
- =begin
- set avatars as default
- Avatar.set_default('User', 123, avatar_id)
- =end
- def self.set_default(object_name, o_id, avatar_id)
- object_id = ObjectLookup.by_name(object_name)
- avatar = Avatar.find_by(
- object_lookup_id: object_id,
- o_id: o_id,
- id: avatar_id,
- )
- avatar.default = true
- avatar.save!
- # set all other to default false
- set_default_items(object_id, o_id, avatar_id)
- avatar
- end
- =begin
- remove all avatars of an object
- Avatar.remove('User', 123)
- =end
- def self.remove(object_name, o_id)
- object_id = ObjectLookup.by_name(object_name)
- Avatar.where(
- object_lookup_id: object_id,
- o_id: o_id,
- ).destroy_all
- object_name_store = "Avatar::#{object_name}"
- Store.remove(
- object: "#{object_name_store}::Full",
- o_id: o_id,
- )
- Store.remove(
- object: "#{object_name_store}::Resize",
- o_id: o_id,
- )
- end
- =begin
- remove one avatars of an object
- Avatar.remove_one('User', 123, avatar_id)
- =end
- def self.remove_one(object_name, o_id, avatar_id)
- object_id = ObjectLookup.by_name(object_name)
- Avatar.where(
- object_lookup_id: object_id,
- o_id: o_id,
- id: avatar_id,
- ).destroy_all
- end
- =begin
- return all avatars of an user
- avatars = Avatar.list('User', 123)
- avatars = Avatar.list('User', 123, no_init_add_as_boolean) # per default true
- avatars = Avatar.list('User', 123, no_init_add_as_boolean, raw: true)
- =end
- def self.list(object_name, o_id, no_init_add_as_boolean = true, raw: false)
- object_id = ObjectLookup.by_name(object_name)
- avatars = Avatar.where(
- object_lookup_id: object_id,
- o_id: o_id,
- ).reorder(initial: :desc, deletable: :asc, created_at: :asc)
- # add initial avatar
- if no_init_add_as_boolean
- _add_init_avatar(object_id, o_id)
- end
- return avatars if raw
- avatar_list = []
- avatars.each do |avatar|
- data = avatar.attributes
- if avatar.store_resize_id
- file = Store.find(avatar.store_resize_id)
- data['content'] = "data:#{file.preferences['Mime-Type']};base64,#{Base64.strict_encode64(file.content)}"
- end
- avatar_list.push data
- end
- avatar_list
- end
- =begin
- get default avatar image of user by hash
- store = Avatar.get_by_hash(hash)
- returns:
- store object
- =end
- def self.get_by_hash(hash)
- avatar = Avatar.find_by(
- store_hash: hash,
- )
- return if !avatar
- Store.find(avatar.store_resize_id)
- end
- =begin
- get default avatar of user by user id
- avatar = Avatar.get_default('User', user_id)
- returns:
- avatar object
- =end
- def self.get_default(object_name, o_id)
- object_id = ObjectLookup.by_name(object_name)
- Avatar.find_by(
- object_lookup_id: object_id,
- o_id: o_id,
- default: true,
- )
- end
- def self.set_default_items(object_id, o_id, avatar_id)
- avatars = Avatar.where(
- object_lookup_id: object_id,
- o_id: o_id,
- ).reorder(created_at: :asc)
- avatars.each do |avatar|
- next if avatar.id == avatar_id
- avatar.default = false
- avatar.save!
- end
- end
- def self._add_init_avatar(object_id, o_id)
- count = Avatar.where(
- object_lookup_id: object_id,
- o_id: o_id,
- ).count
- return if count.positive?
- object_name = ObjectLookup.by_id(object_id)
- return if !object_name.constantize.exists?(id: o_id)
- Avatar.create!(
- o_id: o_id,
- object_lookup_id: object_id,
- default: true,
- source: 'init',
- initial: true,
- deletable: false,
- updated_by_id: 1,
- created_by_id: 1,
- )
- end
- end
|