Added multi avatar support.
This commit is contained in:
parent
2c25123824
commit
aed7e70179
16 changed files with 790 additions and 226 deletions
|
@ -1,19 +1,30 @@
|
||||||
class Index extends App.Controller
|
class Index extends App.Controller
|
||||||
elements:
|
elements:
|
||||||
'.js-upload': 'fileInput'
|
'.js-upload': 'fileInput'
|
||||||
'.avatar-gallery': 'avatarGallery'
|
'.avatar-gallery': 'avatarGallery'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click .js-openCamera': 'openCamera'
|
'click .js-openCamera': 'openCamera'
|
||||||
'change .js-upload': 'onUpload'
|
'change .js-upload': 'onUpload'
|
||||||
'click .avatar': 'onSelect'
|
'click .avatar': 'onSelect'
|
||||||
'click .avatar-delete': 'onDelete'
|
'click .avatar-delete': 'onDelete'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
return if !@authenticate()
|
return if !@authenticate()
|
||||||
|
@avatars = []
|
||||||
|
@loadAvatarList()
|
||||||
|
|
||||||
@render()
|
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
|
# check if the browser supports webcam access
|
||||||
# doesnt render the camera button if not
|
# doesnt render the camera button if not
|
||||||
|
@ -24,6 +35,8 @@ class Index extends App.Controller
|
||||||
render: =>
|
render: =>
|
||||||
@html App.view('profile/avatar')
|
@html App.view('profile/avatar')
|
||||||
webcamSupport: @hasGetUserMedia()
|
webcamSupport: @hasGetUserMedia()
|
||||||
|
avatars: @avatars
|
||||||
|
@$('.avatar[data-id="' + @Session.get('id') + '"]').attr('data-id', '').attr('data-avatar-id', '0')
|
||||||
|
|
||||||
onSelect: (e) =>
|
onSelect: (e) =>
|
||||||
@pick( $(e.currentTarget) )
|
@pick( $(e.currentTarget) )
|
||||||
|
@ -31,33 +44,99 @@ class Index extends App.Controller
|
||||||
onDelete: (e) =>
|
onDelete: (e) =>
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
if confirm App.i18n.translateInline('Delete Avatar?')
|
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()
|
$(e.currentTarget).parent('.avatar-holder').remove()
|
||||||
@pick @('.avatar').last()
|
@pick @$('.avatar').last()
|
||||||
# remove avatar
|
|
||||||
|
# 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) =>
|
pick: (avatar) =>
|
||||||
@$('.avatar').removeClass('is-active')
|
@$('.avatar').removeClass('is-active')
|
||||||
avatar.addClass('is-active')
|
avatar.addClass('is-active')
|
||||||
|
avatar_id = avatar.data('avatar-id')
|
||||||
|
params =
|
||||||
|
id: avatar_id
|
||||||
|
|
||||||
# update avatar globally
|
# 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: =>
|
openCamera: =>
|
||||||
new Camera
|
new Camera
|
||||||
callback: @storeImage
|
callback: @storeImage
|
||||||
|
|
||||||
storeImage: (src) =>
|
storeImage: (src) =>
|
||||||
avatarHolder = $(App.view('profile/avatar-holder') src: src)
|
|
||||||
@avatarGallery.append(avatarHolder)
|
# store avatar globally
|
||||||
@pick avatarHolder.find('.avatar')
|
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) =>
|
onUpload: (event) =>
|
||||||
callback = @storeImage
|
callback = @storeImage
|
||||||
EXIF.getData event.target.files[0], ->
|
EXIF.getData event.target.files[0], ->
|
||||||
orientation = this.exifdata.Orientation
|
orientation = this.exifdata.Orientation
|
||||||
reader = new FileReader()
|
reader = new FileReader()
|
||||||
reader.onload = (e) =>
|
reader.onload = (e) =>
|
||||||
new ImageCropper
|
new ImageCropper
|
||||||
imageSource: e.target.result
|
imageSource: e.target.result
|
||||||
callback: callback
|
callback: callback
|
||||||
orientation: orientation
|
orientation: orientation
|
||||||
|
|
||||||
reader.readAsDataURL(this)
|
reader.readAsDataURL(this)
|
||||||
|
@ -71,9 +150,9 @@ class ImageCropper extends App.ControllerModal
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super
|
super
|
||||||
@head = 'Crop Image'
|
@head = 'Crop Image'
|
||||||
@cancel = true
|
@cancel = true
|
||||||
@button = 'Save'
|
@button = 'Save'
|
||||||
@buttonClass = 'btn--success'
|
@buttonClass = 'btn--success'
|
||||||
|
|
||||||
@show( App.view('profile/imageCropper')() )
|
@show( App.view('profile/imageCropper')() )
|
||||||
|
@ -97,9 +176,9 @@ class ImageCropper extends App.ControllerModal
|
||||||
@image.attr src: @options.imageSource
|
@image.attr src: @options.imageSource
|
||||||
|
|
||||||
orientateImage: (e) =>
|
orientateImage: (e) =>
|
||||||
image = e.currentTarget
|
image = e.currentTarget
|
||||||
canvas = document.createElement('canvas')
|
canvas = document.createElement('canvas')
|
||||||
ctx = canvas.getContext('2d')
|
ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
# fit image into options.max bounding box
|
# fit image into options.max bounding box
|
||||||
# if image.width > @options.max
|
# if image.width > @options.max
|
||||||
|
@ -110,10 +189,10 @@ class ImageCropper extends App.ControllerModal
|
||||||
# image.height = @options.max
|
# image.height = @options.max
|
||||||
|
|
||||||
if @angle is 180
|
if @angle is 180
|
||||||
canvas.width = image.width
|
canvas.width = image.width
|
||||||
canvas.height = image.height
|
canvas.height = image.height
|
||||||
else
|
else
|
||||||
canvas.width = image.height
|
canvas.width = image.height
|
||||||
canvas.height = image.width
|
canvas.height = image.width
|
||||||
|
|
||||||
ctx.translate(canvas.width/2, canvas.height/2)
|
ctx.translate(canvas.width/2, canvas.height/2)
|
||||||
|
@ -143,25 +222,25 @@ class ImageCropper extends App.ControllerModal
|
||||||
|
|
||||||
class Camera extends App.ControllerModal
|
class Camera extends App.ControllerModal
|
||||||
elements:
|
elements:
|
||||||
'.js-shoot': 'shootButton'
|
'.js-shoot': 'shootButton'
|
||||||
'.js-submit': 'submitButton'
|
'.js-submit': 'submitButton'
|
||||||
'.camera-preview': 'preview'
|
'.camera-preview': 'preview'
|
||||||
'.camera': 'camera'
|
'.camera': 'camera'
|
||||||
'video': 'video'
|
'video': 'video'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click .js-shoot:not(.is-disabled)': 'onShootClick'
|
'click .js-shoot:not(.is-disabled)': 'onShootClick'
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super
|
super
|
||||||
@size = 256
|
@size = 256
|
||||||
@photoTaken = false
|
@photoTaken = false
|
||||||
@backgroundColor = 'white'
|
@backgroundColor = 'white'
|
||||||
|
|
||||||
@head = 'Camera'
|
@head = 'Camera'
|
||||||
@cancel = true
|
@cancel = true
|
||||||
@button = 'Save'
|
@button = 'Save'
|
||||||
@buttonClass = 'btn--success is-disabled'
|
@buttonClass = 'btn--success is-disabled'
|
||||||
@centerButtons = [{
|
@centerButtons = [{
|
||||||
className: 'btn--success js-shoot',
|
className: 'btn--success js-shoot',
|
||||||
text: 'Shoot'
|
text: 'Shoot'
|
||||||
|
@ -179,19 +258,19 @@ class Camera extends App.ControllerModal
|
||||||
onShootClick: =>
|
onShootClick: =>
|
||||||
if @photoTaken
|
if @photoTaken
|
||||||
@photoTaken = false
|
@photoTaken = false
|
||||||
@countdown = 0
|
@countdown = 0
|
||||||
@submitButton.addClass('is-disabled')
|
@submitButton.addClass('is-disabled')
|
||||||
@shootButton
|
@shootButton
|
||||||
.removeClass('btn--danger')
|
.removeClass('btn--danger')
|
||||||
.addClass('btn--success')
|
.addClass('btn--success')
|
||||||
.text(App.i18n.translateInline('Shoot'))
|
.text( App.i18n.translateInline('Shoot') )
|
||||||
@updatePreview()
|
@updatePreview()
|
||||||
else
|
else
|
||||||
@shoot()
|
@shoot()
|
||||||
@shootButton
|
@shootButton
|
||||||
.removeClass('btn--success')
|
.removeClass('btn--success')
|
||||||
.addClass('btn--danger')
|
.addClass('btn--danger')
|
||||||
.text(App.i18n.translateInline('Discard'))
|
.text( App.i18n.translateInline('Discard') )
|
||||||
|
|
||||||
shoot: =>
|
shoot: =>
|
||||||
@photoTaken = true
|
@photoTaken = true
|
||||||
|
@ -226,7 +305,7 @@ class Camera extends App.ControllerModal
|
||||||
return
|
return
|
||||||
|
|
||||||
convertToHumanReadable =
|
convertToHumanReadable =
|
||||||
'PermissionDeniedError': App.i18n.translateInline('You have to allow access to your webcam.')
|
'PermissionDeniedError': App.i18n.translateInline('You have to allow access to your webcam.')
|
||||||
'ConstraintNotSatisfiedError': App.i18n.translateInline('No camera found.')
|
'ConstraintNotSatisfiedError': App.i18n.translateInline('No camera found.')
|
||||||
|
|
||||||
alert convertToHumanReadable[error.name]
|
alert convertToHumanReadable[error.name]
|
||||||
|
@ -289,13 +368,14 @@ class Camera extends App.ControllerModal
|
||||||
@video.attr height: ''
|
@video.attr height: ''
|
||||||
|
|
||||||
@cache.attr
|
@cache.attr
|
||||||
width: @video.height()
|
width: @video.height()
|
||||||
height: @video.height()
|
height: @video.height()
|
||||||
|
|
||||||
offsetX = (@video.width() - @video.height())/2
|
offsetX = (@video.width() - @video.height())/2
|
||||||
|
|
||||||
# draw full resolution screenshot
|
# draw full resolution screenshot
|
||||||
@cacheCtx.save()
|
@cacheCtx.save()
|
||||||
|
|
||||||
# flip image
|
# flip image
|
||||||
@cacheCtx.scale(-1,1)
|
@cacheCtx.scale(-1,1)
|
||||||
@cacheCtx.drawImage(@video.get(0), offsetX, 0, -@video.width(), @video.height())
|
@cacheCtx.drawImage(@video.get(0), offsetX, 0, -@video.width(), @video.height())
|
||||||
|
|
|
@ -5,7 +5,7 @@ class App.ImageService
|
||||||
src: (url) =>
|
src: (url) =>
|
||||||
@orgDataURL = url
|
@orgDataURL = url
|
||||||
|
|
||||||
resize: ( x = 'auto', y = 'auto') =>
|
resize: ( x = 'auto', y = 'auto', sizeFactor = 1) =>
|
||||||
@canvas = document.createElement('canvas')
|
@canvas = document.createElement('canvas')
|
||||||
context = @canvas.getContext('2d')
|
context = @canvas.getContext('2d')
|
||||||
|
|
||||||
|
@ -28,7 +28,10 @@ class App.ImageService
|
||||||
factor = imageWidth / y
|
factor = imageWidth / y
|
||||||
x = imageHeight / factor
|
x = imageHeight / factor
|
||||||
|
|
||||||
console.log('BB', x, y)
|
if x < imageWidth || y < imageHeight
|
||||||
|
x = x * sizeFactor
|
||||||
|
y = y * sizeFactor
|
||||||
|
|
||||||
# set canvas dimensions
|
# set canvas dimensions
|
||||||
@canvas.width = x
|
@canvas.width = x
|
||||||
@canvas.height = y
|
@canvas.height = y
|
||||||
|
@ -47,10 +50,10 @@ class App.ImageService
|
||||||
|
|
||||||
toDataURLForAvatar: ( x, y ) =>
|
toDataURLForAvatar: ( x, y ) =>
|
||||||
return if @checkUrl()
|
return if @checkUrl()
|
||||||
@resize( x * 2, y * 2 )
|
@resize( x, y, 2 )
|
||||||
@toDataURL( 'image/jpeg', 0.7 )
|
@toDataURL( 'image/jpeg', 0.7 )
|
||||||
|
|
||||||
toDataURLForApp: ( x, y ) =>
|
toDataURLForApp: ( x, y ) =>
|
||||||
return if @checkUrl()
|
return if @checkUrl()
|
||||||
@resize( x * 2, y * 2 )
|
@resize( x, y, 2 )
|
||||||
@toDataURL( 'image/png', 0.7 )
|
@toDataURL( 'image/png', 0.7 )
|
||||||
|
|
|
@ -58,24 +58,29 @@ class App.User extends App.Model
|
||||||
if placement
|
if placement
|
||||||
placement = "data-placement=\"#{placement}\""
|
placement = "data-placement=\"#{placement}\""
|
||||||
|
|
||||||
if @image is 'none'
|
if !@image || @image is 'none'
|
||||||
return @uniqueAvatar(size, placement, cssClass)
|
return @uniqueAvatar(size, placement, cssClass)
|
||||||
else
|
else
|
||||||
"<span class=\"avatar user-popover #{cssClass}\" data-id=\"#{@id}\" style=\"background-image: url(#{ @imageUrl })\" #{placement}></span>"
|
"<span class=\"avatar user-popover #{cssClass}\" data-id=\"#{@id}\" style=\"background-image: url(#{ @imageUrl })\" #{placement}></span>"
|
||||||
|
|
||||||
uniqueAvatar: (size = 40, placement = '', cssClass = '') ->
|
uniqueAvatar: (size = 40, placement = '', cssClass = '', avatar) ->
|
||||||
if size and !cssClass
|
if size
|
||||||
cssClass += " size-#{ size }"
|
cssClass += " size-#{ size }"
|
||||||
|
|
||||||
width = 300
|
width = 300
|
||||||
height = 226
|
height = 226
|
||||||
size = parseInt(size, 10)
|
size = parseInt(size, 10)
|
||||||
|
|
||||||
rng = new Math.seedrandom(@id)
|
rng = new Math.seedrandom(@id)
|
||||||
x = rng() * (width - size)
|
x = rng() * (width - size)
|
||||||
y = rng() * (height - 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) ->
|
@_fillUp: (data) ->
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="avatar-holder">
|
<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 class="avatar-delete"><div class="delete icon"></div></div>
|
||||||
</div>
|
</div>
|
|
@ -3,21 +3,27 @@
|
||||||
<h1><%- @T( 'Avatar' ) %></h1>
|
<h1><%- @T( 'Avatar' ) %></h1>
|
||||||
<div class="page-header-meta">
|
<div class="page-header-meta">
|
||||||
<% if @webcamSupport: %>
|
<% if @webcamSupport: %>
|
||||||
<div class="btn btn--success js-openCamera">Camera</div>
|
<div class="btn btn--success js-openCamera"><%- @T('Camera') %></div>
|
||||||
<% end %>
|
<% 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>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-gallery horizontal wrap">
|
<div class="avatar-gallery horizontal wrap">
|
||||||
|
<% for avatar in @avatars: %>
|
||||||
<div class="avatar-holder">
|
<div class="avatar-holder">
|
||||||
<%- App.Session.get().uniqueAvatar("50") %>
|
<% 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>
|
||||||
<div class="avatar-holder">
|
<% end %>
|
||||||
<%- App.Session.get().avatar("50", undefined, 'is-active') %>
|
|
||||||
<div class="avatar-delete"><div class="delete icon"></div></div>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="active">
|
|
||||||
<div class="size-50 avatar avatar--new centered"><div class="white plus icon"></div></div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
|
@ -190,29 +190,35 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
# return auth ok
|
# return auth ok
|
||||||
return true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_role( role_name )
|
def is_role( role_name )
|
||||||
return false if !current_user
|
return false if !current_user
|
||||||
return true if current_user.is_role( role_name )
|
return true if current_user.is_role( role_name )
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def ticket_permission(ticket)
|
def ticket_permission(ticket)
|
||||||
return true if ticket.permission( :current_user => current_user )
|
return true if ticket.permission( :current_user => current_user )
|
||||||
|
|
||||||
response_access_deny
|
response_access_deny
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_not_role( role_name )
|
def is_not_role( role_name )
|
||||||
deny_if_not_role( role_name )
|
deny_if_not_role( role_name )
|
||||||
end
|
end
|
||||||
|
|
||||||
def deny_if_not_role( role_name )
|
def deny_if_not_role( role_name )
|
||||||
return false if is_role( role_name )
|
return false if is_role( role_name )
|
||||||
response_access_deny
|
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
|
end
|
||||||
|
|
||||||
def response_access_deny
|
def response_access_deny
|
||||||
|
@ -220,7 +226,7 @@ class ApplicationController < ActionController::Base
|
||||||
:json => {},
|
:json => {},
|
||||||
:status => :unauthorized
|
:status => :unauthorized
|
||||||
)
|
)
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def config_frontend
|
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] )
|
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.'
|
messages[:logo] = 'Unable to process image upload.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -106,7 +106,7 @@ curl http://localhost/api/v1/getting_started -v -u #{login}:#{password}
|
||||||
file = StaticAssets.data_url_attributes( params[:logo] )
|
file = StaticAssets.data_url_attributes( params[:logo] )
|
||||||
|
|
||||||
# store image 1:1
|
# store image 1:1
|
||||||
StaticAssets.store_raw( file[:content], file[:content_type] )
|
StaticAssets.store_raw( file[:content], file[:mime_type] )
|
||||||
end
|
end
|
||||||
|
|
||||||
if params[:logo_resize] && params[:logo_resize] =~ /^data:image/i
|
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] )
|
file = StaticAssets.data_url_attributes( params[:logo_resize] )
|
||||||
|
|
||||||
# store image 1:1
|
# 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
|
end
|
||||||
|
|
||||||
# set changed settings
|
# set changed settings
|
||||||
|
|
|
@ -155,7 +155,7 @@ class SessionsController < ApplicationController
|
||||||
current_user_set(authorization.user)
|
current_user_set(authorization.user)
|
||||||
|
|
||||||
# log new session
|
# 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
|
# remember last login date
|
||||||
authorization.user.update_last_login
|
authorization.user.update_last_login
|
||||||
|
|
|
@ -95,12 +95,7 @@ curl http://localhost/api/v1/users/#{id}.json -v -u #{login}:#{password}
|
||||||
def show
|
def show
|
||||||
|
|
||||||
# access deny
|
# access deny
|
||||||
if is_role('Customer') && !is_role('Admin') && !is_role('Agent')
|
return if !permission_check
|
||||||
if params[:id].to_i != current_user.id
|
|
||||||
response_access_deny
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if params[:full]
|
if params[:full]
|
||||||
full = User.full( params[:id] )
|
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 do assignment as defined
|
||||||
else
|
else
|
||||||
|
|
||||||
|
# permission check by role
|
||||||
|
return if !permission_check_by_role
|
||||||
|
|
||||||
if params[:role_ids]
|
if params[:role_ids]
|
||||||
user.role_ids = params[:role_ids]
|
user.role_ids = params[:role_ids]
|
||||||
end
|
end
|
||||||
|
@ -278,13 +277,8 @@ curl http://localhost/api/v1/users/2.json -v -u #{login}:#{password} -H "Content
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|
||||||
# allow user to update him self
|
# access deny
|
||||||
if is_role('Customer') && !is_role('Admin') && !is_role('Agent')
|
return if !permission_check
|
||||||
if params[:id] != current_user.id
|
|
||||||
response_access_deny
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
user = User.find( params[:id] )
|
user = User.find( params[:id] )
|
||||||
|
|
||||||
|
@ -606,23 +600,143 @@ curl http://localhost/api/v1/users/image/8d6cca1c6bdc226cf2ba131e264ca2c7 -v -u
|
||||||
def image
|
def image
|
||||||
|
|
||||||
# cache image
|
# cache image
|
||||||
response.headers['Expires'] = 1.year.from_now.httpdate
|
response.headers['Expires'] = 1.year.from_now.httpdate
|
||||||
response.headers["Cache-Control"] = "cache, store, max-age=31536000, must-revalidate"
|
response.headers['Cache-Control'] = 'cache, store, max-age=31536000, must-revalidate'
|
||||||
response.headers["Pragma"] = "cache"
|
response.headers['Pragma'] = 'cache'
|
||||||
|
|
||||||
user = User.where( :image => params[:hash] ).first
|
file = Avatar.get_by_hash( params[:hash] )
|
||||||
if user
|
if file
|
||||||
image = user.get_image
|
|
||||||
send_data(
|
send_data(
|
||||||
image[:content],
|
file.content,
|
||||||
:filename => image[:filename],
|
:filename => file.filename,
|
||||||
:type => image[:content_type],
|
:type => file.preferences['Content-Type'] || file.preferences['Mime-Type'],
|
||||||
:disposition => 'inline'
|
:disposition => 'inline'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
end
|
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
|
||||||
|
|
||||||
end
|
end
|
|
@ -14,35 +14,60 @@ class Authorization < ApplicationModel
|
||||||
|
|
||||||
# update auth tokens
|
# update auth tokens
|
||||||
auth.update_attributes(
|
auth.update_attributes(
|
||||||
:token => hash['credentials']['token'],
|
:token => hash['credentials']['token'],
|
||||||
:secret => hash['credentials']['secret']
|
:secret => hash['credentials']['secret']
|
||||||
)
|
)
|
||||||
|
|
||||||
# update username of auth entry if empty
|
# update username of auth entry if empty
|
||||||
if !auth.username && hash['info']['nickname']
|
if !auth.username && hash['info']['nickname']
|
||||||
auth.update_attributes(
|
auth.update_attributes(
|
||||||
:username => hash['info']['nickname'],
|
:username => hash['info']['nickname'],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# update image if needed
|
# update image if needed
|
||||||
if hash['info']['image']
|
if hash['info']['image']
|
||||||
user = User.find( auth.user_id )
|
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
|
end
|
||||||
end
|
end
|
||||||
return auth
|
auth
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create_from_hash(hash, user = nil)
|
def self.create_from_hash(hash, user = nil)
|
||||||
if user then
|
if user
|
||||||
user.update_attributes(
|
|
||||||
# :username => hash['username'],
|
# save/update avatar
|
||||||
:image_source => hash['info']['image']
|
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
|
# fillup empty attributes
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
@ -50,7 +75,7 @@ class Authorization < ApplicationModel
|
||||||
user = User.create_from_hash!(hash)
|
user = User.create_from_hash!(hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
auth = Authorization.create(
|
Authorization.create(
|
||||||
:user => user,
|
:user => user,
|
||||||
:uid => hash['uid'],
|
:uid => hash['uid'],
|
||||||
:username => hash['info']['nickname'] || hash['username'],
|
:username => hash['info']['nickname'] || hash['username'],
|
||||||
|
@ -58,12 +83,12 @@ class Authorization < ApplicationModel
|
||||||
:token => hash['credentials']['token'],
|
:token => hash['credentials']['token'],
|
||||||
:secret => hash['credentials']['secret']
|
:secret => hash['credentials']['secret']
|
||||||
)
|
)
|
||||||
return auth
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def delete_user_cache
|
|
||||||
self.user.cache_delete
|
def delete_user_cache
|
||||||
end
|
self.user.cache_delete
|
||||||
|
end
|
||||||
|
|
||||||
end
|
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
|
extend User::Search
|
||||||
include User::SearchIndex
|
include User::SearchIndex
|
||||||
|
|
||||||
before_create :check_name, :check_email, :check_login, :check_image, :check_password
|
before_create :check_name, :check_email, :check_login, :check_password
|
||||||
before_update :check_password, :check_image, :check_email, :check_login_update
|
before_update :check_password, :check_email
|
||||||
after_create :check_image_load, :notify_clients_after_create
|
after_create :avatar_check, :notify_clients_after_create
|
||||||
after_update :check_image_load, :notify_clients_after_update
|
after_update :avatar_check, :notify_clients_after_update
|
||||||
after_destroy :notify_clients_after_destroy
|
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 :groups, :after_add => :cache_update, :after_remove => :cache_update
|
||||||
has_and_belongs_to_many :roles, :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
|
self.save
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def check_name
|
def check_name
|
||||||
|
@ -472,7 +430,7 @@ returns
|
||||||
while check
|
while check
|
||||||
exists = User.where( :login => self.login ).first
|
exists = User.where( :login => self.login ).first
|
||||||
if exists
|
if exists
|
||||||
self.login = self.login + rand(99).to_s
|
self.login = self.login + rand(999).to_s
|
||||||
else
|
else
|
||||||
check = false
|
check = false
|
||||||
end
|
end
|
||||||
|
@ -480,59 +438,30 @@ returns
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# FIXME: Remove me later
|
def avatar_check
|
||||||
def check_login_update
|
|
||||||
if self.login
|
|
||||||
self.login = self.login.downcase
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_image
|
return if !self.email
|
||||||
if !self.image_source || self.image_source == '' || self.image_source =~ /gravatar.com/i
|
return if self.email.empty?
|
||||||
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
|
|
||||||
|
|
||||||
def check_image_load
|
# save/update avatar
|
||||||
|
avatar = Avatar.auto_detection(
|
||||||
return if !self.image_source
|
:object => 'User',
|
||||||
return if self.image_source !~ /http/i
|
:o_id => self.id,
|
||||||
|
:url => self.email,
|
||||||
# download image
|
:source => 'app',
|
||||||
response = UserAgent.request( self.image_source )
|
:updated_by_id => self.updated_by_id,
|
||||||
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',
|
|
||||||
:o_id => self.id,
|
|
||||||
:data => response.body,
|
|
||||||
:filename => 'image',
|
|
||||||
:preferences => {
|
|
||||||
'Content-Type' => response.content_type
|
|
||||||
},
|
|
||||||
:created_by_id => self.updated_by_id,
|
:created_by_id => self.updated_by_id,
|
||||||
)
|
)
|
||||||
self.cache_delete
|
|
||||||
|
# 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
|
end
|
||||||
|
|
||||||
def check_password
|
def check_password
|
||||||
|
@ -542,16 +471,17 @@ returns
|
||||||
|
|
||||||
# get current record
|
# get current record
|
||||||
if self.id
|
if self.id
|
||||||
current = User.find(self.id)
|
#current = User.find(self.id)
|
||||||
self.password = current.password
|
#self.password = current.password
|
||||||
|
self.password = self.password_was
|
||||||
end
|
end
|
||||||
|
|
||||||
# create crypted password if not already crypted
|
end
|
||||||
else
|
|
||||||
if self.password !~ /^\{sha2\}/
|
# crypt password if not already crypted
|
||||||
crypted = Digest::SHA2.hexdigest( self.password )
|
if self.password && self.password !~ /^\{sha2\}/
|
||||||
self.password = "{sha2}#{crypted}"
|
crypted = Digest::SHA2.hexdigest( self.password )
|
||||||
end
|
self.password = "{sha2}#{crypted}"
|
||||||
end
|
end
|
||||||
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/password_change', :to => 'users#password_change', :via => :post
|
||||||
match api_path + '/users/preferences', :to => 'users#preferences', :via => :put
|
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/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', :to => 'users#index', :via => :get
|
||||||
match api_path + '/users/:id', :to => 'users#show', :via => :get
|
match api_path + '/users/:id', :to => 'users#show', :via => :get
|
||||||
match api_path + '/users/history/:id', :to => 'users#history', :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,8 +3,8 @@ module StaticAssets
|
||||||
def self.data_url_attributes( data_url )
|
def self.data_url_attributes( data_url )
|
||||||
data = {}
|
data = {}
|
||||||
if data_url =~ /^data:(.+?);base64,(.+?)$/
|
if data_url =~ /^data:(.+?);base64,(.+?)$/
|
||||||
data[:content_type] = $1
|
data[:mime_type] = $1
|
||||||
data[:content] = Base64.decode64($2)
|
data[:content] = Base64.decode64($2)
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
raise "Unable to parse data url: #{data_url.substr(0,100)}"
|
raise "Unable to parse data url: #{data_url.substr(0,100)}"
|
||||||
|
|
|
@ -17,7 +17,7 @@ class UserTest < ActiveSupport::TestCase
|
||||||
:create_verify => {
|
:create_verify => {
|
||||||
:firstname => 'Firstname',
|
:firstname => 'Firstname',
|
||||||
:lastname => 'Lastname',
|
:lastname => 'Lastname',
|
||||||
:image => 'none',
|
:image => nil,
|
||||||
:email => 'some@example.com',
|
:email => 'some@example.com',
|
||||||
:login => 'some@example.com',
|
:login => 'some@example.com',
|
||||||
},
|
},
|
||||||
|
@ -35,7 +35,7 @@ class UserTest < ActiveSupport::TestCase
|
||||||
:create_verify => {
|
:create_verify => {
|
||||||
:firstname => 'Firstname',
|
:firstname => 'Firstname',
|
||||||
:lastname => 'Lastname',
|
:lastname => 'Lastname',
|
||||||
:image => 'none',
|
:image => nil,
|
||||||
:email => 'some@example.com',
|
:email => 'some@example.com',
|
||||||
:login => 'some@example.com',
|
:login => 'some@example.com',
|
||||||
},
|
},
|
||||||
|
@ -53,7 +53,7 @@ class UserTest < ActiveSupport::TestCase
|
||||||
:create_verify => {
|
:create_verify => {
|
||||||
:firstname => 'Firstname',
|
:firstname => 'Firstname',
|
||||||
:lastname => 'Lastname',
|
:lastname => 'Lastname',
|
||||||
:image => 'none',
|
:image => nil,
|
||||||
:email => 'some@example.com',
|
:email => 'some@example.com',
|
||||||
:login => 'some@example.com',
|
:login => 'some@example.com',
|
||||||
},
|
},
|
||||||
|
@ -139,8 +139,7 @@ class UserTest < ActiveSupport::TestCase
|
||||||
:create_verify => {
|
:create_verify => {
|
||||||
:firstname => 'Bob',
|
:firstname => 'Bob',
|
||||||
:lastname => 'Smith',
|
:lastname => 'Smith',
|
||||||
:image => 'none',
|
:image => nil,
|
||||||
:image_md5 => '76fdc28c07e4f3d7802b75aacfccdf6a',
|
|
||||||
:email => 'bob.smith@example.com',
|
:email => 'bob.smith@example.com',
|
||||||
:login => 'login-4',
|
:login => 'login-4',
|
||||||
},
|
},
|
||||||
|
@ -203,8 +202,8 @@ class UserTest < ActiveSupport::TestCase
|
||||||
assert_equal( value, user[key], "create check #{ key } in (#{ test[:name] })" )
|
assert_equal( value, user[key], "create check #{ key } in (#{ test[:name] })" )
|
||||||
}
|
}
|
||||||
if test[:create_verify][:image_md5]
|
if test[:create_verify][:image_md5]
|
||||||
file = user.get_image
|
file = Avatar.get_by_hash( user.image )
|
||||||
file_md5 = Digest::MD5.hexdigest( file[:content] )
|
file_md5 = Digest::MD5.hexdigest( file.content )
|
||||||
assert_equal( test[:create_verify][:image_md5], file_md5, "create avatar md5 check in (#{ test[:name] })" )
|
assert_equal( test[:create_verify][:image_md5], file_md5, "create avatar md5 check in (#{ test[:name] })" )
|
||||||
end
|
end
|
||||||
if test[:update]
|
if test[:update]
|
||||||
|
@ -216,8 +215,8 @@ class UserTest < ActiveSupport::TestCase
|
||||||
}
|
}
|
||||||
|
|
||||||
if test[:update_verify][:image_md5]
|
if test[:update_verify][:image_md5]
|
||||||
file = user.get_image
|
file = Avatar.get_by_hash( user.image )
|
||||||
file_md5 = Digest::MD5.hexdigest( file[:content] )
|
file_md5 = Digest::MD5.hexdigest( file.content )
|
||||||
assert_equal( test[:update_verify][:image_md5], file_md5, "update avatar md5 check in (#{ test[:name] })" )
|
assert_equal( test[:update_verify][:image_md5], file_md5, "update avatar md5 check in (#{ test[:name] })" )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue