Added multi avatar support.
This commit is contained in:
parent
2c25123824
commit
aed7e70179
16 changed files with 790 additions and 226 deletions
|
@ -12,8 +12,19 @@ class Index extends App.Controller
|
|||
constructor: ->
|
||||
super
|
||||
return if !@authenticate()
|
||||
@avatars = []
|
||||
@loadAvatarList()
|
||||
|
||||
loadAvatarList: =>
|
||||
@ajax(
|
||||
id: 'avatar_list'
|
||||
type: 'GET'
|
||||
url: @apiPath + '/users/avatar'
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@avatars = data.avatars
|
||||
@render()
|
||||
)
|
||||
|
||||
# check if the browser supports webcam access
|
||||
# doesnt render the camera button if not
|
||||
|
@ -24,6 +35,8 @@ class Index extends App.Controller
|
|||
render: =>
|
||||
@html App.view('profile/avatar')
|
||||
webcamSupport: @hasGetUserMedia()
|
||||
avatars: @avatars
|
||||
@$('.avatar[data-id="' + @Session.get('id') + '"]').attr('data-id', '').attr('data-avatar-id', '0')
|
||||
|
||||
onSelect: (e) =>
|
||||
@pick( $(e.currentTarget) )
|
||||
|
@ -31,23 +44,89 @@ class Index extends App.Controller
|
|||
onDelete: (e) =>
|
||||
e.stopPropagation()
|
||||
if confirm App.i18n.translateInline('Delete Avatar?')
|
||||
|
||||
params =
|
||||
id: $(e.currentTarget).parent('.avatar-holder').find('.avatar').data('avatar-id')
|
||||
|
||||
$(e.currentTarget).parent('.avatar-holder').remove()
|
||||
@pick @('.avatar').last()
|
||||
# remove avatar
|
||||
@pick @$('.avatar').last()
|
||||
|
||||
# remove avatar globally
|
||||
@ajax(
|
||||
id: 'avatar_delete'
|
||||
type: 'DELETE'
|
||||
url: @apiPath + '/users/avatar'
|
||||
data: JSON.stringify( params )
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
)
|
||||
|
||||
pick: (avatar) =>
|
||||
@$('.avatar').removeClass('is-active')
|
||||
avatar.addClass('is-active')
|
||||
avatar_id = avatar.data('avatar-id')
|
||||
params =
|
||||
id: avatar_id
|
||||
|
||||
# update avatar globally
|
||||
@ajax(
|
||||
id: 'avatar_set_default'
|
||||
type: 'POST'
|
||||
url: @apiPath + '/users/avatar/set'
|
||||
data: JSON.stringify( params )
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
# update avatar in app at runtime
|
||||
activeAvatar = @$('.avatar.is-active')
|
||||
style = activeAvatar.attr('style')
|
||||
|
||||
# set correct background size
|
||||
if activeAvatar.text()
|
||||
style += ';background-size:auto'
|
||||
else
|
||||
style += ';background-size:cover'
|
||||
|
||||
# find old avatars and update them
|
||||
replaceAvatar = $('.avatar[data-id="' + @Session.get('id') + '"]')
|
||||
replaceAvatar.attr('style', style)
|
||||
|
||||
# update avatar text if needed
|
||||
if activeAvatar.text()
|
||||
replaceAvatar.text(activeAvatar.text())
|
||||
replaceAvatar.addClass('unique')
|
||||
else
|
||||
replaceAvatar.text( '' )
|
||||
replaceAvatar.removeClass('unique')
|
||||
)
|
||||
avatar
|
||||
|
||||
openCamera: =>
|
||||
new Camera
|
||||
callback: @storeImage
|
||||
|
||||
storeImage: (src) =>
|
||||
avatarHolder = $(App.view('profile/avatar-holder') src: src)
|
||||
|
||||
# store avatar globally
|
||||
params =
|
||||
avatar_full: src
|
||||
|
||||
# add resized image
|
||||
avatar = new App.ImageService( src )
|
||||
params['avatar_resize'] = avatar.toDataURLForAvatar( 'auto', 160 )
|
||||
|
||||
# store on server site
|
||||
@ajax(
|
||||
id: 'avatar_new'
|
||||
type: 'POST'
|
||||
url: @apiPath + '/users/avatar'
|
||||
data: JSON.stringify( params )
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
avatarHolder = $(App.view('profile/avatar-holder')( src: src, avatar: data.avatar ) )
|
||||
@avatarGallery.append(avatarHolder)
|
||||
@pick avatarHolder.find('.avatar')
|
||||
)
|
||||
|
||||
onUpload: (event) =>
|
||||
callback = @storeImage
|
||||
|
@ -296,6 +375,7 @@ class Camera extends App.ControllerModal
|
|||
|
||||
# draw full resolution screenshot
|
||||
@cacheCtx.save()
|
||||
|
||||
# flip image
|
||||
@cacheCtx.scale(-1,1)
|
||||
@cacheCtx.drawImage(@video.get(0), offsetX, 0, -@video.width(), @video.height())
|
||||
|
|
|
@ -5,7 +5,7 @@ class App.ImageService
|
|||
src: (url) =>
|
||||
@orgDataURL = url
|
||||
|
||||
resize: ( x = 'auto', y = 'auto') =>
|
||||
resize: ( x = 'auto', y = 'auto', sizeFactor = 1) =>
|
||||
@canvas = document.createElement('canvas')
|
||||
context = @canvas.getContext('2d')
|
||||
|
||||
|
@ -28,7 +28,10 @@ class App.ImageService
|
|||
factor = imageWidth / y
|
||||
x = imageHeight / factor
|
||||
|
||||
console.log('BB', x, y)
|
||||
if x < imageWidth || y < imageHeight
|
||||
x = x * sizeFactor
|
||||
y = y * sizeFactor
|
||||
|
||||
# set canvas dimensions
|
||||
@canvas.width = x
|
||||
@canvas.height = y
|
||||
|
@ -47,10 +50,10 @@ class App.ImageService
|
|||
|
||||
toDataURLForAvatar: ( x, y ) =>
|
||||
return if @checkUrl()
|
||||
@resize( x * 2, y * 2 )
|
||||
@resize( x, y, 2 )
|
||||
@toDataURL( 'image/jpeg', 0.7 )
|
||||
|
||||
toDataURLForApp: ( x, y ) =>
|
||||
return if @checkUrl()
|
||||
@resize( x * 2, y * 2 )
|
||||
@resize( x, y, 2 )
|
||||
@toDataURL( 'image/png', 0.7 )
|
||||
|
|
|
@ -58,13 +58,13 @@ class App.User extends App.Model
|
|||
if placement
|
||||
placement = "data-placement=\"#{placement}\""
|
||||
|
||||
if @image is 'none'
|
||||
if !@image || @image is 'none'
|
||||
return @uniqueAvatar(size, placement, cssClass)
|
||||
else
|
||||
"<span class=\"avatar user-popover #{cssClass}\" data-id=\"#{@id}\" style=\"background-image: url(#{ @imageUrl })\" #{placement}></span>"
|
||||
|
||||
uniqueAvatar: (size = 40, placement = '', cssClass = '') ->
|
||||
if size and !cssClass
|
||||
uniqueAvatar: (size = 40, placement = '', cssClass = '', avatar) ->
|
||||
if size
|
||||
cssClass += " size-#{ size }"
|
||||
|
||||
width = 300
|
||||
|
@ -75,7 +75,12 @@ class App.User extends App.Model
|
|||
x = rng() * (width - size)
|
||||
y = rng() * (height - size)
|
||||
|
||||
"<span class=\"avatar unique user-popover #{cssClass}\" data-id=\"#{@id}\" style=\"background-position: -#{ x }px -#{ y }px;\" #{placement}>#{ @initials() }</span>"
|
||||
if !avatar
|
||||
cssClass += "#{cssClass} user-popover"
|
||||
data = "data-id=\"#{@id}\""
|
||||
else
|
||||
data = "data-avatar-id=\"#{avatar.id}\""
|
||||
"<span class=\"avatar unique #{cssClass}\" #{data} style=\"background-position: -#{ x }px -#{ y }px;\" #{placement}>#{ @initials() }</span>"
|
||||
|
||||
@_fillUp: (data) ->
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="avatar-holder">
|
||||
<span class="avatar size-50" style="background-image: url(<%- @src %>)"></span>
|
||||
<span class="avatar size-50" data-avatar-id="<%- @avatar.id %>" style="background-image: url(<%- @src %>)"></span>
|
||||
<div class="avatar-delete"><div class="delete icon"></div></div>
|
||||
</div>
|
|
@ -3,21 +3,27 @@
|
|||
<h1><%- @T( 'Avatar' ) %></h1>
|
||||
<div class="page-header-meta">
|
||||
<% if @webcamSupport: %>
|
||||
<div class="btn btn--success js-openCamera">Camera</div>
|
||||
<div class="btn btn--success js-openCamera"><%- @T('Camera') %></div>
|
||||
<% end %>
|
||||
<div class="btn btn--success fileUpload">Upload<input type="file" class="js-upload" accept="image/*"></div>
|
||||
<div class="btn btn--success fileUpload"><%- @T('Upload') %><input type="file" class="js-upload" accept="image/*"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="avatar-gallery horizontal wrap">
|
||||
<% for avatar in @avatars: %>
|
||||
<div class="avatar-holder">
|
||||
<%- App.Session.get().uniqueAvatar("50") %>
|
||||
</div>
|
||||
<div class="avatar-holder">
|
||||
<%- App.Session.get().avatar("50", undefined, 'is-active') %>
|
||||
<% if avatar.inital: %>
|
||||
<% cssClass = '' %>
|
||||
<% if avatar.default: %>
|
||||
<% cssClass = 'is-active' %>
|
||||
<% end %>
|
||||
<%- App.Session.get().uniqueAvatar('50', '', cssClass, avatar) %>
|
||||
<% else: %>
|
||||
<span class="avatar size-50 <% if avatar.default: %>is-active<% end %>" data-avatar-id="<%- avatar.id %>" style="background-image: url(<%- avatar.content %>)"></span>
|
||||
<% if avatar.deletable: %>
|
||||
<div class="avatar-delete"><div class="delete icon"></div></div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<!-- <div class="active">
|
||||
<div class="size-50 avatar avatar--new centered"><div class="white plus icon"></div></div>
|
||||
</div> -->
|
||||
<% end %>
|
||||
</div>
|
|
@ -190,29 +190,35 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
# return auth ok
|
||||
return true
|
||||
true
|
||||
end
|
||||
|
||||
def is_role( role_name )
|
||||
return false if !current_user
|
||||
return true if current_user.is_role( role_name )
|
||||
return false
|
||||
false
|
||||
end
|
||||
|
||||
def ticket_permission(ticket)
|
||||
return true if ticket.permission( :current_user => current_user )
|
||||
|
||||
response_access_deny
|
||||
return false
|
||||
false
|
||||
end
|
||||
|
||||
def is_not_role( role_name )
|
||||
deny_if_not_role( role_name )
|
||||
end
|
||||
|
||||
def deny_if_not_role( role_name )
|
||||
return false if is_role( role_name )
|
||||
response_access_deny
|
||||
return true
|
||||
true
|
||||
end
|
||||
|
||||
def valid_session_with_user
|
||||
return true if current_user
|
||||
render :json => { :message => 'No session user!' }, :status => :unprocessable_entity
|
||||
false
|
||||
end
|
||||
|
||||
def response_access_deny
|
||||
|
@ -220,7 +226,7 @@ class ApplicationController < ActionController::Base
|
|||
:json => {},
|
||||
:status => :unauthorized
|
||||
)
|
||||
return false
|
||||
false
|
||||
end
|
||||
|
||||
def config_frontend
|
||||
|
|
|
@ -71,7 +71,7 @@ curl http://localhost/api/v1/getting_started -v -u #{login}:#{password}
|
|||
|
||||
file = StaticAssets.data_url_attributes( params[:logo] )
|
||||
|
||||
if !file[:content] || !file[:content_type]
|
||||
if !file[:content] || !file[:mime_type]
|
||||
messages[:logo] = 'Unable to process image upload.'
|
||||
end
|
||||
end
|
||||
|
@ -106,7 +106,7 @@ curl http://localhost/api/v1/getting_started -v -u #{login}:#{password}
|
|||
file = StaticAssets.data_url_attributes( params[:logo] )
|
||||
|
||||
# store image 1:1
|
||||
StaticAssets.store_raw( file[:content], file[:content_type] )
|
||||
StaticAssets.store_raw( file[:content], file[:mime_type] )
|
||||
end
|
||||
|
||||
if params[:logo_resize] && params[:logo_resize] =~ /^data:image/i
|
||||
|
@ -115,7 +115,7 @@ curl http://localhost/api/v1/getting_started -v -u #{login}:#{password}
|
|||
file = StaticAssets.data_url_attributes( params[:logo_resize] )
|
||||
|
||||
# store image 1:1
|
||||
settings[:product_logo] = StaticAssets.store( file[:content], file[:content_type] )
|
||||
settings[:product_logo] = StaticAssets.store( file[:content], file[:mime_type] )
|
||||
end
|
||||
|
||||
# set changed settings
|
||||
|
|
|
@ -155,7 +155,7 @@ class SessionsController < ApplicationController
|
|||
current_user_set(authorization.user)
|
||||
|
||||
# log new session
|
||||
user.activity_stream_log( 'session started', authorization.user.id, true )
|
||||
authorization.user.activity_stream_log( 'session started', authorization.user.id, true )
|
||||
|
||||
# remember last login date
|
||||
authorization.user.update_last_login
|
||||
|
|
|
@ -95,12 +95,7 @@ curl http://localhost/api/v1/users/#{id}.json -v -u #{login}:#{password}
|
|||
def show
|
||||
|
||||
# access deny
|
||||
if is_role('Customer') && !is_role('Admin') && !is_role('Agent')
|
||||
if params[:id].to_i != current_user.id
|
||||
response_access_deny
|
||||
return
|
||||
end
|
||||
end
|
||||
return if !permission_check
|
||||
|
||||
if params[:full]
|
||||
full = User.full( params[:id] )
|
||||
|
@ -175,6 +170,10 @@ curl http://localhost/api/v1/users.json -v -u #{login}:#{password} -H "Content-T
|
|||
|
||||
# else do assignment as defined
|
||||
else
|
||||
|
||||
# permission check by role
|
||||
return if !permission_check_by_role
|
||||
|
||||
if params[:role_ids]
|
||||
user.role_ids = params[:role_ids]
|
||||
end
|
||||
|
@ -278,13 +277,8 @@ curl http://localhost/api/v1/users/2.json -v -u #{login}:#{password} -H "Content
|
|||
|
||||
def update
|
||||
|
||||
# allow user to update him self
|
||||
if is_role('Customer') && !is_role('Admin') && !is_role('Agent')
|
||||
if params[:id] != current_user.id
|
||||
response_access_deny
|
||||
return
|
||||
end
|
||||
end
|
||||
# access deny
|
||||
return if !permission_check
|
||||
|
||||
user = User.find( params[:id] )
|
||||
|
||||
|
@ -607,22 +601,142 @@ curl http://localhost/api/v1/users/image/8d6cca1c6bdc226cf2ba131e264ca2c7 -v -u
|
|||
|
||||
# cache image
|
||||
response.headers['Expires'] = 1.year.from_now.httpdate
|
||||
response.headers["Cache-Control"] = "cache, store, max-age=31536000, must-revalidate"
|
||||
response.headers["Pragma"] = "cache"
|
||||
response.headers['Cache-Control'] = 'cache, store, max-age=31536000, must-revalidate'
|
||||
response.headers['Pragma'] = 'cache'
|
||||
|
||||
user = User.where( :image => params[:hash] ).first
|
||||
if user
|
||||
image = user.get_image
|
||||
file = Avatar.get_by_hash( params[:hash] )
|
||||
if file
|
||||
send_data(
|
||||
image[:content],
|
||||
:filename => image[:filename],
|
||||
:type => image[:content_type],
|
||||
file.content,
|
||||
:filename => file.filename,
|
||||
:type => file.preferences['Content-Type'] || file.preferences['Mime-Type'],
|
||||
:disposition => 'inline'
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
render :json => {}, :status => 404
|
||||
# serve default image
|
||||
image = 'R0lGODdhMAAwAOMAAMzMzJaWlr6+vqqqqqOjo8XFxbe3t7GxsZycnAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAMAAwAAAEcxDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru98TwuAA+KQAQqJK8EAgBAgMEqmkzUgBIeSwWGZtR5XhSqAULACCoGCJGwlm1MGQrq9RqgB8fm4ZTUgDBIEcRR9fz6HiImKi4yNjo+QkZKTlJWWkBEAOw=='
|
||||
send_data(
|
||||
Base64.decode64(image),
|
||||
:filename => 'image.gif',
|
||||
:type => 'image/gif',
|
||||
:disposition => 'inline'
|
||||
)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
Resource:
|
||||
POST /api/v1/users/avatar
|
||||
|
||||
Payload:
|
||||
{
|
||||
"avatar_full": "base64 url",
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
:message => 'ok'
|
||||
}
|
||||
|
||||
Test:
|
||||
curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"avatar": "base64 url"}'
|
||||
|
||||
=end
|
||||
|
||||
def avatar_new
|
||||
return if !valid_session_with_user
|
||||
|
||||
# get & validate image
|
||||
file_full = StaticAssets.data_url_attributes( params[:avatar_full] )
|
||||
file_resize = StaticAssets.data_url_attributes( params[:avatar_resize] )
|
||||
|
||||
avatar = Avatar.add(
|
||||
:object => 'User',
|
||||
:o_id => current_user.id,
|
||||
:full => {
|
||||
:content => file_full[:content],
|
||||
:mime_type => file_full[:mime_type],
|
||||
},
|
||||
:resize => {
|
||||
:content => file_resize[:content],
|
||||
:mime_type => file_resize[:mime_type],
|
||||
},
|
||||
:source => 'upload ' + Time.now.to_s,
|
||||
:deletable => true,
|
||||
)
|
||||
|
||||
# update user link
|
||||
current_user.update_attributes( :image => avatar.store_hash )
|
||||
|
||||
render :json => { :avatar => avatar }, :status => :ok
|
||||
end
|
||||
|
||||
def avatar_set_default
|
||||
return if !valid_session_with_user
|
||||
|
||||
# get & validate image
|
||||
if !params[:id]
|
||||
render :json => { :message => 'No id of avatar!' }, :status => :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
# set as default
|
||||
avatar = Avatar.set_default( 'User', current_user.id, params[:id] )
|
||||
|
||||
# update user link
|
||||
current_user.update_attributes( :image => avatar.store_hash )
|
||||
|
||||
render :json => {}, :status => :ok
|
||||
end
|
||||
|
||||
def avatar_destroy
|
||||
return if !valid_session_with_user
|
||||
|
||||
# get & validate image
|
||||
if !params[:id]
|
||||
render :json => { :message => 'No id of avatar!' }, :status => :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
# remove avatar
|
||||
Avatar.remove_one( 'User', current_user.id, params[:id] )
|
||||
|
||||
# update user link
|
||||
avatar = Avatar.get_default( 'User', current_user.id )
|
||||
current_user.update_attributes( :image => avatar.store_hash )
|
||||
|
||||
render :json => {}, :status => :ok
|
||||
end
|
||||
|
||||
def avatar_list
|
||||
return if !valid_session_with_user
|
||||
|
||||
# list of avatars
|
||||
result = Avatar.list( 'User', current_user.id )
|
||||
render :json => { :avatars => result }, :status => :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def permission_check_by_role
|
||||
return true if is_role('Admin')
|
||||
return true if is_role('Agent')
|
||||
|
||||
response_access_deny
|
||||
return false
|
||||
end
|
||||
|
||||
def permission_check
|
||||
return true if is_role('Admin')
|
||||
return true if is_role('Agent')
|
||||
|
||||
# allow to update customer by him self
|
||||
return true if is_role('Customer') && params[:id].to_i == current_user.id
|
||||
|
||||
response_access_deny
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
|
@ -28,21 +28,46 @@ class Authorization < ApplicationModel
|
|||
# update image if needed
|
||||
if hash['info']['image']
|
||||
user = User.find( auth.user_id )
|
||||
user.update_attributes(
|
||||
:image_source => hash['info']['image']
|
||||
|
||||
# save/update avatar
|
||||
avatar = Avatar.add(
|
||||
:object => 'User',
|
||||
:o_id => user.id,
|
||||
:url => hash['info']['image'],
|
||||
:source => hash['provider'],
|
||||
:deletable => true,
|
||||
:updated_by_id => user.id,
|
||||
:created_by_id => user.id,
|
||||
)
|
||||
|
||||
# update user link
|
||||
if avatar
|
||||
user.update_column( :image, avatar.store_hash )
|
||||
end
|
||||
end
|
||||
return auth
|
||||
end
|
||||
auth
|
||||
end
|
||||
|
||||
def self.create_from_hash(hash, user = nil)
|
||||
if user then
|
||||
user.update_attributes(
|
||||
# :username => hash['username'],
|
||||
:image_source => hash['info']['image']
|
||||
if user
|
||||
|
||||
# save/update avatar
|
||||
avatar = Avatar.add(
|
||||
:object => 'User',
|
||||
:o_id => user.id,
|
||||
:url => hash['info']['image'],
|
||||
:source => hash['provider'],
|
||||
:deletable => true,
|
||||
:updated_by_id => user.id,
|
||||
:created_by_id => user.id,
|
||||
)
|
||||
|
||||
# update user link
|
||||
if avatar
|
||||
user.update_column( :image, avatar.store_hash )
|
||||
end
|
||||
|
||||
# fillup empty attributes
|
||||
# TODO
|
||||
|
||||
|
@ -50,7 +75,7 @@ class Authorization < ApplicationModel
|
|||
user = User.create_from_hash!(hash)
|
||||
end
|
||||
|
||||
auth = Authorization.create(
|
||||
Authorization.create(
|
||||
:user => user,
|
||||
:uid => hash['uid'],
|
||||
:username => hash['info']['nickname'] || hash['username'],
|
||||
|
@ -58,10 +83,10 @@ class Authorization < ApplicationModel
|
|||
:token => hash['credentials']['token'],
|
||||
:secret => hash['credentials']['secret']
|
||||
)
|
||||
return auth
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_user_cache
|
||||
self.user.cache_delete
|
||||
end
|
||||
|
|
363
app/models/avatar.rb
Normal file
363
app/models/avatar.rb
Normal file
|
@ -0,0 +1,363 @@
|
|||
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Avatar < ApplicationModel
|
||||
belongs_to :object_lookup, :class_name => 'ObjectLookup'
|
||||
|
||||
=begin
|
||||
|
||||
add a avatar based on auto detection (email address)
|
||||
|
||||
Avatar.auto_detection(
|
||||
:object => 'User',
|
||||
:o_id => user.id,
|
||||
:url => 'somebody@example.com',
|
||||
:source => 'web',
|
||||
:updated_by_id => 1,
|
||||
:created_by_id => 1,
|
||||
)
|
||||
|
||||
=end
|
||||
|
||||
def self.auto_detection(data)
|
||||
return if !data[:url]
|
||||
return if data[:url].empty?
|
||||
|
||||
# dry gravatar lookup
|
||||
hash = Digest::MD5.hexdigest(data[:url])
|
||||
url = "http://www.gravatar.com/avatar/#{hash}.jpg?s=160&d=404"
|
||||
puts "#{data[:url]}: #{url}"
|
||||
|
||||
Avatar.add(
|
||||
:object => data[:object],
|
||||
:o_id => data[:o_id],
|
||||
:url => url,
|
||||
:source => 'gravatar.com',
|
||||
:deletable => false,
|
||||
:updated_by_id => 1,
|
||||
:created_by_id => 1,
|
||||
)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
add a avatar
|
||||
|
||||
Avatar.add(
|
||||
:object => 'User',
|
||||
:o_id => user.id,
|
||||
:default => true,
|
||||
:full => {
|
||||
:content => '...',
|
||||
:mime_type => 'image/png',
|
||||
},
|
||||
:resize => {
|
||||
:content => '...',
|
||||
:mime_type => 'image/png',
|
||||
},
|
||||
:source => 'web',
|
||||
: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 inital 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],
|
||||
:inital => 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] && !data[:source].empty?
|
||||
avatar_already_exists = Avatar.where(
|
||||
:object_lookup_id => object_id,
|
||||
:o_id => data[:o_id],
|
||||
:source => data[:source],
|
||||
).first
|
||||
end
|
||||
|
||||
# fetch image
|
||||
if data[:url] && data[:url] =~ /^http/
|
||||
|
||||
# check if source ist already updated within last 2 minutes
|
||||
if avatar_already_exists
|
||||
if avatar_already_exists.source_url == data[:url]
|
||||
if avatar_already_exists.updated_at > 2.minutes.ago
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# twitter workaround to get bigger avatar images
|
||||
# see also https://dev.twitter.com/overview/general/user-profile-images-and-banners
|
||||
if data[:url] =~ /\/\/pbs.twimg.com\//i
|
||||
data[:url].sub!(/normal\.png$/, 'bigger.png')
|
||||
end
|
||||
|
||||
# fetch image
|
||||
response = UserAgent.request( data[:url] )
|
||||
if !response.success?
|
||||
#puts "WARNING: Can't fetch '#{self.image_source}' (maybe no avatar available), http code: #{response.code.to_s}"
|
||||
#raise "Can't fetch '#{self.image_source}', http code: #{response.code.to_s}"
|
||||
return
|
||||
end
|
||||
#puts "NOTICE: Fetch '#{self.image_source}', http code: #{response.code.to_s}"
|
||||
mime_type = 'image'
|
||||
if data[:url] =~ /\.png/i
|
||||
mime_type = 'image/png'
|
||||
end
|
||||
if data[:url] =~ /\.(jpg|jpeg)/i
|
||||
mime_type = 'image/png'
|
||||
end
|
||||
if !data[:resize]
|
||||
data[:resize] = {}
|
||||
end
|
||||
data[:resize][:content] = response.body
|
||||
data[:resize][:mime_type] = mime_type
|
||||
if !data[:full]
|
||||
data[:full] = {}
|
||||
end
|
||||
data[:full][:content] = response.body
|
||||
data[:full][:mime_type] = mime_type
|
||||
end
|
||||
|
||||
# check if avatar need to be updated
|
||||
record[:store_hash] = Digest::MD5.hexdigest( data[:resize][:content] )
|
||||
if avatar_already_exists
|
||||
return if avatar_already_exists.store_hash == record[:store_hash]
|
||||
end
|
||||
|
||||
# store images
|
||||
object_name = "Avatar::#{data[:object]}"
|
||||
if data[:full]
|
||||
store_full = Store.add(
|
||||
: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]
|
||||
store_resize = Store.add(
|
||||
: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
|
||||
|
||||
# update existing
|
||||
if avatar_already_exists
|
||||
avatar_already_exists.update_attributes( 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.where(
|
||||
:object_lookup_id => object_id,
|
||||
:o_id => o_id,
|
||||
:id => avatar_id,
|
||||
).first
|
||||
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 )
|
||||
|
||||
=end
|
||||
|
||||
def self.list(object_name, o_id)
|
||||
object_id = ObjectLookup.by_name( object_name )
|
||||
avatars = Avatar.where(
|
||||
:object_lookup_id => object_id,
|
||||
:o_id => o_id,
|
||||
).order( 'inital DESC, deletable ASC, created_at ASC, id DESC' )
|
||||
|
||||
# add inital avatar
|
||||
add_init_avatar(object_id, o_id)
|
||||
|
||||
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.where(
|
||||
:store_hash => hash,
|
||||
).first
|
||||
return if !avatar
|
||||
file = 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.where(
|
||||
:object_lookup_id => object_id,
|
||||
:o_id => o_id,
|
||||
:default => true,
|
||||
).first
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.set_default_items(object_id, o_id, avatar_id)
|
||||
avatars = Avatar.where(
|
||||
:object_lookup_id => object_id,
|
||||
:o_id => o_id,
|
||||
).order( 'created_at ASC, id DESC' )
|
||||
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 > 0
|
||||
|
||||
Avatar.create(
|
||||
:o_id => o_id,
|
||||
:object_lookup_id => object_id,
|
||||
:default => true,
|
||||
:source => 'init',
|
||||
:inital => true,
|
||||
:deletable => false,
|
||||
:updated_by_id => 1,
|
||||
:created_by_id => 1,
|
||||
)
|
||||
end
|
||||
end
|
|
@ -8,11 +8,11 @@ class User < ApplicationModel
|
|||
extend User::Search
|
||||
include User::SearchIndex
|
||||
|
||||
before_create :check_name, :check_email, :check_login, :check_image, :check_password
|
||||
before_update :check_password, :check_image, :check_email, :check_login_update
|
||||
after_create :check_image_load, :notify_clients_after_create
|
||||
after_update :check_image_load, :notify_clients_after_update
|
||||
after_destroy :notify_clients_after_destroy
|
||||
before_create :check_name, :check_email, :check_login, :check_password
|
||||
before_update :check_password, :check_email
|
||||
after_create :avatar_check, :notify_clients_after_create
|
||||
after_update :avatar_check, :notify_clients_after_update
|
||||
after_destroy :avatar_destroy, :notify_clients_after_destroy
|
||||
|
||||
has_and_belongs_to_many :groups, :after_add => :cache_update, :after_remove => :cache_update
|
||||
has_and_belongs_to_many :roles, :after_add => :cache_update, :after_remove => :cache_update
|
||||
|
@ -382,48 +382,6 @@ returns
|
|||
self.save
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
get image of user
|
||||
|
||||
user = User.find(123)
|
||||
result = user.get_image
|
||||
|
||||
returns
|
||||
|
||||
result = {
|
||||
:filename => 'some filename',
|
||||
:content_type => 'image/png',
|
||||
:content => bin_string,
|
||||
}
|
||||
|
||||
=end
|
||||
|
||||
def get_image
|
||||
|
||||
# find file
|
||||
list = Store.list( :object => 'User::Image', :o_id => self.id )
|
||||
logger.debug list.inspect
|
||||
if list && list[0]
|
||||
file = Store.find( list[0] )
|
||||
result = {
|
||||
:content => file.content,
|
||||
:filename => file.filename,
|
||||
:content_type => file.preferences['Content-Type'] || file.preferences['Mime-Type'],
|
||||
}
|
||||
return result
|
||||
end
|
||||
|
||||
# serve default image
|
||||
image = 'R0lGODdhMAAwAOMAAMzMzJaWlr6+vqqqqqOjo8XFxbe3t7GxsZycnAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAMAAwAAAEcxDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru98TwuAA+KQAQqJK8EAgBAgMEqmkzUgBIeSwWGZtR5XhSqAULACCoGCJGwlm1MGQrq9RqgB8fm4ZTUgDBIEcRR9fz6HiImKi4yNjo+QkZKTlJWWkBEAOw=='
|
||||
result = {
|
||||
:content => Base64.decode64(image),
|
||||
:filename => 'image.gif',
|
||||
:content_type => 'image/gif',
|
||||
}
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_name
|
||||
|
@ -472,7 +430,7 @@ returns
|
|||
while check
|
||||
exists = User.where( :login => self.login ).first
|
||||
if exists
|
||||
self.login = self.login + rand(99).to_s
|
||||
self.login = self.login + rand(999).to_s
|
||||
else
|
||||
check = false
|
||||
end
|
||||
|
@ -480,60 +438,31 @@ returns
|
|||
end
|
||||
end
|
||||
|
||||
# FIXME: Remove me later
|
||||
def check_login_update
|
||||
if self.login
|
||||
self.login = self.login.downcase
|
||||
end
|
||||
end
|
||||
def avatar_check
|
||||
|
||||
def check_image
|
||||
if !self.image_source || self.image_source == '' || self.image_source =~ /gravatar.com/i
|
||||
if self.email
|
||||
hash = Digest::MD5.hexdigest(self.email)
|
||||
self.image_source = "http://www.gravatar.com/avatar/#{hash}?s=160&d=404"
|
||||
logger.debug "#{self.email}: #{self.image_source}"
|
||||
end
|
||||
end
|
||||
end
|
||||
return if !self.email
|
||||
return if self.email.empty?
|
||||
|
||||
def check_image_load
|
||||
|
||||
return if !self.image_source
|
||||
return if self.image_source !~ /http/i
|
||||
|
||||
# download image
|
||||
response = UserAgent.request( self.image_source )
|
||||
if !response.success?
|
||||
self.update_column( :image, 'none' )
|
||||
self.cache_delete
|
||||
#puts "WARNING: Can't fetch '#{self.image_source}' (maybe no avatar available), http code: #{response.code.to_s}"
|
||||
#raise "Can't fetch '#{self.image_source}', http code: #{response.code.to_s}"
|
||||
return
|
||||
end
|
||||
#puts "NOTICE: Fetch '#{self.image_source}', http code: #{response.code.to_s}"
|
||||
|
||||
# store image local
|
||||
hash = Digest::MD5.hexdigest( response.body )
|
||||
|
||||
# check if image has changed
|
||||
return if self.image == hash
|
||||
#puts "NOTICE: update image in store"
|
||||
# save new image
|
||||
self.update_column( :image, hash )
|
||||
Store.remove( :object => 'User::Image', :o_id => self.id )
|
||||
Store.add(
|
||||
:object => 'User::Image',
|
||||
# save/update avatar
|
||||
avatar = Avatar.auto_detection(
|
||||
:object => 'User',
|
||||
:o_id => self.id,
|
||||
:data => response.body,
|
||||
:filename => 'image',
|
||||
:preferences => {
|
||||
'Content-Type' => response.content_type
|
||||
},
|
||||
:url => self.email,
|
||||
:source => 'app',
|
||||
:updated_by_id => self.updated_by_id,
|
||||
:created_by_id => self.updated_by_id,
|
||||
)
|
||||
|
||||
# update user link
|
||||
if avatar
|
||||
self.update_column( :image, avatar.store_hash )
|
||||
self.cache_delete
|
||||
end
|
||||
end
|
||||
|
||||
def avatar_destroy
|
||||
Avatar.remove( 'User', self.id )
|
||||
end
|
||||
|
||||
def check_password
|
||||
|
||||
|
@ -542,16 +471,17 @@ returns
|
|||
|
||||
# get current record
|
||||
if self.id
|
||||
current = User.find(self.id)
|
||||
self.password = current.password
|
||||
#current = User.find(self.id)
|
||||
#self.password = current.password
|
||||
self.password = self.password_was
|
||||
end
|
||||
|
||||
# create crypted password if not already crypted
|
||||
else
|
||||
if self.password !~ /^\{sha2\}/
|
||||
end
|
||||
|
||||
# crypt password if not already crypted
|
||||
if self.password && self.password !~ /^\{sha2\}/
|
||||
crypted = Digest::SHA2.hexdigest( self.password )
|
||||
self.password = "{sha2}#{crypted}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,12 @@ Zammad::Application.routes.draw do
|
|||
match api_path + '/users/password_change', :to => 'users#password_change', :via => :post
|
||||
match api_path + '/users/preferences', :to => 'users#preferences', :via => :put
|
||||
match api_path + '/users/account', :to => 'users#account_remove', :via => :delete
|
||||
|
||||
match api_path + '/users/avatar', :to => 'users#avatar_new', :via => :post
|
||||
match api_path + '/users/avatar', :to => 'users#avatar_list', :via => :get
|
||||
match api_path + '/users/avatar', :to => 'users#avatar_destroy', :via => :delete
|
||||
match api_path + '/users/avatar/set', :to => 'users#avatar_set_default', :via => :post
|
||||
|
||||
match api_path + '/users', :to => 'users#index', :via => :get
|
||||
match api_path + '/users/:id', :to => 'users#show', :via => :get
|
||||
match api_path + '/users/history/:id', :to => 'users#history', :via => :get
|
||||
|
|
27
db/migrate/20141126000001_create_avatar.rb
Normal file
27
db/migrate/20141126000001_create_avatar.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class CreateAvatar < ActiveRecord::Migration
|
||||
def up
|
||||
create_table :avatars do |t|
|
||||
t.column :o_id, :integer, :null => false
|
||||
t.column :object_lookup_id, :integer, :null => false
|
||||
t.column :default, :boolean, :null => false, :default => false
|
||||
t.column :deletable, :boolean, :null => false, :default => true
|
||||
t.column :inital, :boolean, :null => false, :default => false
|
||||
t.column :store_full_id, :integer, :null => true
|
||||
t.column :store_resize_id, :integer, :null => true
|
||||
t.column :store_hash, :string, :limit => 32, :null => true
|
||||
t.column :source, :string, :limit => 100, :null => false
|
||||
t.column :source_url, :string, :limit => 512, :null => true
|
||||
t.column :updated_by_id, :integer, :null => false
|
||||
t.column :created_by_id, :integer, :null => false
|
||||
t.timestamps
|
||||
end
|
||||
add_index :avatars, [:o_id, :object_lookup_id]
|
||||
add_index :avatars, [:store_hash]
|
||||
add_index :avatars, [:source]
|
||||
add_index :avatars, [:source_url]
|
||||
add_index :avatars, [:default]
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@ module StaticAssets
|
|||
def self.data_url_attributes( data_url )
|
||||
data = {}
|
||||
if data_url =~ /^data:(.+?);base64,(.+?)$/
|
||||
data[:content_type] = $1
|
||||
data[:mime_type] = $1
|
||||
data[:content] = Base64.decode64($2)
|
||||
return data
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ class UserTest < ActiveSupport::TestCase
|
|||
:create_verify => {
|
||||
:firstname => 'Firstname',
|
||||
:lastname => 'Lastname',
|
||||
:image => 'none',
|
||||
:image => nil,
|
||||
:email => 'some@example.com',
|
||||
:login => 'some@example.com',
|
||||
},
|
||||
|
@ -35,7 +35,7 @@ class UserTest < ActiveSupport::TestCase
|
|||
:create_verify => {
|
||||
:firstname => 'Firstname',
|
||||
:lastname => 'Lastname',
|
||||
:image => 'none',
|
||||
:image => nil,
|
||||
:email => 'some@example.com',
|
||||
:login => 'some@example.com',
|
||||
},
|
||||
|
@ -53,7 +53,7 @@ class UserTest < ActiveSupport::TestCase
|
|||
:create_verify => {
|
||||
:firstname => 'Firstname',
|
||||
:lastname => 'Lastname',
|
||||
:image => 'none',
|
||||
:image => nil,
|
||||
:email => 'some@example.com',
|
||||
:login => 'some@example.com',
|
||||
},
|
||||
|
@ -139,8 +139,7 @@ class UserTest < ActiveSupport::TestCase
|
|||
:create_verify => {
|
||||
:firstname => 'Bob',
|
||||
:lastname => 'Smith',
|
||||
:image => 'none',
|
||||
:image_md5 => '76fdc28c07e4f3d7802b75aacfccdf6a',
|
||||
:image => nil,
|
||||
:email => 'bob.smith@example.com',
|
||||
:login => 'login-4',
|
||||
},
|
||||
|
@ -203,8 +202,8 @@ class UserTest < ActiveSupport::TestCase
|
|||
assert_equal( value, user[key], "create check #{ key } in (#{ test[:name] })" )
|
||||
}
|
||||
if test[:create_verify][:image_md5]
|
||||
file = user.get_image
|
||||
file_md5 = Digest::MD5.hexdigest( file[:content] )
|
||||
file = Avatar.get_by_hash( user.image )
|
||||
file_md5 = Digest::MD5.hexdigest( file.content )
|
||||
assert_equal( test[:create_verify][:image_md5], file_md5, "create avatar md5 check in (#{ test[:name] })" )
|
||||
end
|
||||
if test[:update]
|
||||
|
@ -216,8 +215,8 @@ class UserTest < ActiveSupport::TestCase
|
|||
}
|
||||
|
||||
if test[:update_verify][:image_md5]
|
||||
file = user.get_image
|
||||
file_md5 = Digest::MD5.hexdigest( file[:content] )
|
||||
file = Avatar.get_by_hash( user.image )
|
||||
file_md5 = Digest::MD5.hexdigest( file.content )
|
||||
assert_equal( test[:update_verify][:image_md5], file_md5, "update avatar md5 check in (#{ test[:name] })" )
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue