mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
Fix specs for avatars
Implement avatar picker Correct avatar related jobs
This commit is contained in:
parent
a864f8aefd
commit
504cfcff96
27 changed files with 228 additions and 158 deletions
|
@ -9,6 +9,23 @@
|
|||
**/
|
||||
export default Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
||||
|
||||
selectedUploadId: function(){
|
||||
switch(this.get("selected")){
|
||||
case "system":
|
||||
return this.get("system_avatar_upload_id");
|
||||
break;
|
||||
case "gravatar":
|
||||
return this.get("gravatar_avatar_upload_id");
|
||||
break;
|
||||
default:
|
||||
return this.get("custom_avatar_upload_id");
|
||||
}
|
||||
}.property(
|
||||
'selected',
|
||||
'system_avatar_upload_id',
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'),
|
||||
|
||||
actions: {
|
||||
useUploadedAvatar: function() {
|
||||
this.set("selected", "uploaded");
|
||||
|
@ -18,6 +35,16 @@ export default Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
|||
},
|
||||
useSystem: function() {
|
||||
this.set("selected", "system");
|
||||
},
|
||||
refreshGravatar: function(){
|
||||
var self = this;
|
||||
self.set("gravatarRefreshDisabled", true);
|
||||
Discourse
|
||||
.ajax("/user_avatar/" + this.get("username") + "/refresh_gravatar", {method: 'POST'})
|
||||
.then(function(result){
|
||||
self.set("gravatarRefreshDisabled", false);
|
||||
self.set("gravatar_avatar_upload_id", result.upload_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -216,25 +216,35 @@ Handlebars.registerHelper('avatar', function(user, options) {
|
|||
|
||||
/**
|
||||
Bound avatar helper.
|
||||
Will rerender whenever the "avatar_template" changes.
|
||||
|
||||
@method boundAvatar
|
||||
@for Handlebars
|
||||
**/
|
||||
Ember.Handlebars.registerBoundHelper('boundAvatar', function(user, options) {
|
||||
Ember.Handlebars.registerBoundHelper('boundAvatar', function(user, size, uploadId) {
|
||||
|
||||
var username = Em.get(user, 'username');
|
||||
|
||||
console.log(options.hash);
|
||||
if(arguments.length < 4){
|
||||
uploadId = Em.get(user, 'uploaded_avatar_id');
|
||||
}
|
||||
|
||||
var uploadId = (options.hash.uploadId && Em.get(user, options.hash.uploadId)) || Em.get(user, 'uploaded_avatar_id');
|
||||
var avatarTemplate = Discourse.User.avatarTemplate(username,uploadId);
|
||||
|
||||
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
|
||||
size: options.hash.imageSize,
|
||||
size: size,
|
||||
avatarTemplate: avatarTemplate
|
||||
}));
|
||||
}, 'uploadId', 'username', 'uploaded_avatar_id');
|
||||
}, 'uploaded_avatar_id');
|
||||
|
||||
/*
|
||||
* Used when we only have a template
|
||||
*/
|
||||
Ember.Handlebars.registerBoundHelper('boundAvatarTemplate', function(avatarTemplate, size) {
|
||||
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
|
||||
size: size,
|
||||
avatarTemplate: avatarTemplate
|
||||
}));
|
||||
});
|
||||
|
||||
/**
|
||||
Nicely format a date without binding or returning HTML
|
||||
|
|
|
@ -332,15 +332,12 @@ Discourse.User = Discourse.Model.extend({
|
|||
|
||||
/*
|
||||
Change avatar selection
|
||||
|
||||
@method toggleAvatarSelection
|
||||
@param {Boolean} useUploadedAvatar true if the user is using the uploaded avatar
|
||||
@returns {Promise} the result of the toggle avatar selection
|
||||
*/
|
||||
toggleAvatarSelection: function(useUploadedAvatar) {
|
||||
return Discourse.ajax("/users/" + this.get("username_lower") + "/preferences/avatar/toggle", {
|
||||
pickAvatar: function(uploadId) {
|
||||
this.set("uploaded_avatar_id", uploadId);
|
||||
return Discourse.ajax("/users/" + this.get("username_lower") + "/preferences/avatar/pick", {
|
||||
type: 'PUT',
|
||||
data: { use_uploaded_avatar: useUploadedAvatar }
|
||||
data: { upload_id: uploadId }
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -403,7 +400,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
return this.get('can_delete_account') && ((this.get('reply_count')||0) + (this.get('topic_count')||0)) <= 1;
|
||||
}.property('can_delete_account', 'reply_count', 'topic_count'),
|
||||
|
||||
delete: function() {
|
||||
"delete": function() {
|
||||
if (this.get('can_delete_account')) {
|
||||
return Discourse.ajax("/users/" + this.get('username'), {
|
||||
type: 'DELETE',
|
||||
|
@ -419,7 +416,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
Discourse.User.reopenClass(Discourse.Singleton, {
|
||||
|
||||
avatarTemplate: function(username, uploadedAvatarId){
|
||||
return Discourse.getURL("/avatar/" + username.toLowerCase() + "/{size}/" + uploadedAvatarId + ".png");
|
||||
return Discourse.getURL("/user_avatar/" + username.toLowerCase() + "/{size}/" + uploadedAvatarId + ".png");
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,30 +20,46 @@ Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
|
|||
showAvatarSelector: function() {
|
||||
Discourse.Route.showModal(this, 'avatar-selector');
|
||||
// all the properties needed for displaying the avatar selector modal
|
||||
this.controllerFor('avatar-selector').setProperties(this.modelFor('user').getProperties(
|
||||
var controller = this.controllerFor('avatar-selector');
|
||||
var user = this.modelFor('user');
|
||||
var props = user.getProperties(
|
||||
'username', 'email',
|
||||
'uploaded_avatar_id',
|
||||
'system_avatar_upload_id',
|
||||
'gravatr_avatar_upload_id',
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
)
|
||||
);
|
||||
|
||||
switch(props.uploaded_avatar_id){
|
||||
case props.system_avatar_upload_id:
|
||||
props.selected = "system";
|
||||
break;
|
||||
case props.gravatar_avatar_upload_id:
|
||||
props.selected = "gravatar";
|
||||
break;
|
||||
default:
|
||||
props.selected = "uploaded";
|
||||
}
|
||||
|
||||
controller.setProperties(props);
|
||||
},
|
||||
|
||||
saveAvatarSelection: function() {
|
||||
var user = this.modelFor('user');
|
||||
var avatarSelector = this.controllerFor('avatar-selector');
|
||||
|
||||
|
||||
// sends the information to the server if it has changed
|
||||
if (avatarSelector.get('use_uploaded_avatar') !== user.get('use_uploaded_avatar')) {
|
||||
user.toggleAvatarSelection(avatarSelector.get('use_uploaded_avatar'));
|
||||
if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) {
|
||||
user.pickAvatar(avatarSelector.get('selectedUploadId'));
|
||||
}
|
||||
|
||||
// saves the data back
|
||||
user.setProperties(avatarSelector.getProperties(
|
||||
'has_uploaded_avatar',
|
||||
'use_uploaded_avatar',
|
||||
'gravatar_template',
|
||||
'uploaded_avatar_template'
|
||||
'system_avatar_upload_id',
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
));
|
||||
user.set('avatar_template', avatarSelector.get('avatarTemplate'));
|
||||
avatarSelector.send('closeModal');
|
||||
},
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
href="#"
|
||||
title='{{i18n user.avatar.title}}'
|
||||
id="current-user">
|
||||
{{boundAvatar currentUser imageSize="medium"}}
|
||||
{{boundAvatar currentUser "medium"}}
|
||||
</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
|
|
@ -2,18 +2,22 @@
|
|||
<div>
|
||||
<div>
|
||||
<input type="radio" id="system-avatar" name="avatar" value="system" {{action useSystem}}>
|
||||
<label class="radio" for="system-avatar">{{boundAvatar controller imageSize="large" uploadId="system_avatar_upload_id"}} {{{i18n user.change_avatar.letter_based}}}</label>
|
||||
<label class="radio" for="system-avatar">{{boundAvatar controller "large" system_avatar_upload_id}} {{{i18n user.change_avatar.letter_based}}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="gravatar" name="avatar" value="gravatar" {{action useGravatar}}>
|
||||
<label class="radio" for="gravatar">{{boundAvatar controller imageSize="large" uploadId="gravatar_avatar_upload_id"}} {{{i18n user.change_avatar.gravatar}}} {{email}}</label>
|
||||
<a href="#" {{action refreshGravatar}} title="{{i18n user.change_avatar.refresh_gravatar_title}}" class="btn no-text"><i class="fa fa-refresh"></i></a>
|
||||
<label class="radio" for="gravatar">{{boundAvatar controller "large" gravatar_avatar_upload_id}} {{{i18n user.change_avatar.gravatar}}} {{email}}</label>
|
||||
<button href="#" {{action refreshGravatar}} title="{{i18n user.change_avatar.refresh_gravatar_title}}" {{bind-attr enabled="view.gravatarRefreshEnabled"}} class="btn no-text"><i class="fa fa-refresh"></i></button>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded_avatar" {{action useUploadedAvatar}}>
|
||||
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded" {{action useUploadedAvatar}}>
|
||||
<label class="radio" for="uploaded_avatar">
|
||||
{{#if custom_avatar_upload_id}}
|
||||
{{boundAvatar controller imageSize="large" uploadId="custom_avatar_upload_id"}} {{i18n user.change_avatar.uploaded_avatar}}
|
||||
{{#if view.hasUploadedAvatar}}
|
||||
{{#if view.uploadedAvatarTemplate}}
|
||||
{{boundAvatarTemplate view.uploadedAvatarTemplate "large"}}
|
||||
{{else}}
|
||||
{{boundAvatar controller "large" custom_avatar_upload_id}} {{i18n user.change_avatar.uploaded_avatar}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{i18n user.change_avatar.uploaded_avatar_empty}}
|
||||
{{/if}}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="revision-details">
|
||||
{{i18n post.revisions.details.edited_by}} {{boundAvatar content imageSize="small"}} {{username}} <span class="date">{{date created_at}}</span> {{#if edit_reason}} — <span class="edit-reason">{{edit_reason}}</span>{{/if}}
|
||||
{{i18n post.revisions.details.edited_by}} {{boundAvatar content "small"}} {{username}} <span class="date">{{date created_at}}</span> {{#if edit_reason}} — <span class="edit-reason">{{edit_reason}}</span>{{/if}}
|
||||
</div>
|
||||
<div id="revisions">
|
||||
{{#if title_changes}}
|
||||
|
@ -32,7 +32,7 @@
|
|||
{{/if}}
|
||||
{{#if user_changes}}
|
||||
<div class="row">
|
||||
{{boundAvatar user_changes.previous imageSize="small"}} {{user_changes.previous.username}} → {{boundAvatar user_changes.current imageSize="small"}} {{user_changes.current.username}}
|
||||
{{boundAvatar user_changes.previous "small"}} {{user_changes.previous.username}} → {{boundAvatar user_changes.current imageSize="small"}} {{user_changes.current.username}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if wiki_changes}}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{#if model}}
|
||||
{{#link-to 'user' user}}{{boundAvatar model imageSize="huge"}}{{/link-to}}
|
||||
{{#link-to 'user' user}}{{boundAvatar model "huge"}}{{/link-to}}
|
||||
|
||||
<div class="names">
|
||||
<h1 {{bind-attr class="staff new_user"}}><a {{bind-attr href="usernameUrl"}}>{{username}}</a></h1>
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
<div class="control-group">
|
||||
<label class="control-label">{{i18n user.avatar.title}}</label>
|
||||
<div class="controls">
|
||||
{{boundAvatar model imageSize="large"}}
|
||||
{{boundAvatar model "large"}}
|
||||
{{#if allowAvatarUpload}}
|
||||
<button {{action showAvatarSelector}} class="btn pad-left no-text"><i class="fa fa-pencil"></i></button>
|
||||
{{else}}
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
<div class='details'>
|
||||
<div class='primary'>
|
||||
{{boundAvatar model imageSize="huge"}}
|
||||
{{boundAvatar model "huge"}}
|
||||
|
||||
<div class="primary-textual">
|
||||
<h1>{{username}} {{{statusIcon}}}</h1>
|
||||
|
|
|
@ -12,11 +12,12 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
|
|||
title: I18n.t('user.change_avatar.title'),
|
||||
uploading: false,
|
||||
uploadProgress: 0,
|
||||
useGravatar: Em.computed.not("controller.use_uploaded_avatar"),
|
||||
canSaveAvatarSelection: Em.computed.or("useGravatar", "controller.has_uploaded_avatar"),
|
||||
saveDisabled: Em.computed.not("canSaveAvatarSelection"),
|
||||
saveDisabled: false,
|
||||
gravatarRefreshEnabled: Em.computed.not('controller.gravatarRefreshDisabled'),
|
||||
imageIsNotASquare : false,
|
||||
|
||||
hasUploadedAvatar: Em.computed.or('uploadedAvatarTemplate', 'controller.custom_avatar_upload_id'),
|
||||
|
||||
didInsertElement: function() {
|
||||
var self = this;
|
||||
var $upload = $("#avatar-input");
|
||||
|
@ -61,10 +62,8 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
|
|||
// make sure we have a url
|
||||
if (data.result.url) {
|
||||
// indicates the users is using an uploaded avatar
|
||||
self.get("controller").setProperties({
|
||||
has_uploaded_avatar: true,
|
||||
use_uploaded_avatar: true
|
||||
});
|
||||
self.set("controller.custom_avatar_upload_id", data.result.upload_id);
|
||||
|
||||
// display a warning whenever the image is not a square
|
||||
self.set("imageIsNotASquare", data.result.width !== data.result.height);
|
||||
// in order to be as much responsive as possible, we're cheating a bit here
|
||||
|
@ -72,7 +71,7 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
|
|||
// often, this file is not a square, so we need to crop it properly
|
||||
// this will also capture the first frame of animated avatars when they're not allowed
|
||||
Discourse.Utilities.cropAvatar(data.result.url, data.files[0].type).then(function(avatarTemplate) {
|
||||
self.get("controller").set("uploaded_avatar_template", avatarTemplate);
|
||||
self.set("uploadedAvatarTemplate", avatarTemplate);
|
||||
});
|
||||
} else {
|
||||
bootbox.alert(I18n.t('post.errors.upload'));
|
||||
|
@ -95,14 +94,15 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
|
|||
$("#avatar-input").fileupload("destroy");
|
||||
},
|
||||
|
||||
// *HACK* used to select the proper radio button
|
||||
// *HACK* used to select the proper radio button, cause {{action}}
|
||||
// stops the default behavior
|
||||
selectedChanged: function() {
|
||||
var self = this;
|
||||
Em.run.next(function() {
|
||||
var value = self.get('controller.use_uploaded_avatar') ? 'uploaded_avatar' : 'gravatar';
|
||||
var value = self.get('controller.selected');
|
||||
$('input:radio[name="avatar"]').val([value]);
|
||||
});
|
||||
}.observes('controller.use_uploaded_avatar'),
|
||||
}.observes('controller.selected'),
|
||||
|
||||
uploadButtonText: function() {
|
||||
return this.get("uploading") ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture");
|
||||
|
|
|
@ -1,7 +1,23 @@
|
|||
require_dependency 'letter_avatar'
|
||||
class AvatarController < ApplicationController
|
||||
|
||||
skip_before_filter :check_xhr, :verify_authenticity_token
|
||||
class UserAvatarsController < ApplicationController
|
||||
skip_before_filter :check_xhr, :verify_authenticity_token, only: :show
|
||||
|
||||
def refresh_gravatar
|
||||
|
||||
user = User.find_by(username_lower: params[:username].downcase)
|
||||
guardian.ensure_can_edit!(user)
|
||||
|
||||
if user
|
||||
user.create_user_avatar(user_id: user.id) unless user.user_avatar
|
||||
user.user_avatar.update_gravatar!
|
||||
|
||||
render json: {upload_id: user.user_avatar.gravatar_upload_id}
|
||||
else
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def show
|
||||
username = params[:username].to_s
|
||||
|
@ -17,17 +33,17 @@ class AvatarController < ApplicationController
|
|||
|
||||
raise Discourse::NotFound unless version > 0 && user_avatar = user.user_avatar
|
||||
|
||||
upload = version if user_avatar.contains_upload?(version)
|
||||
upload = Upload.find(version) if user_avatar.contains_upload?(version)
|
||||
upload ||= user.uploaded_avatar if user.uploaded_avatar_id == version
|
||||
|
||||
if user.uploaded_avatar && !upload
|
||||
return redirect_to "/avatar/#{user.username_lower}/#{size}/#{user.uploaded_avatar_id}.png"
|
||||
elsif upload
|
||||
# TODO broken with S3 (should retrun a permanent redirect)
|
||||
original = Discourse.store.path_for(user.uploaded_avatar)
|
||||
original = Discourse.store.path_for(upload)
|
||||
if File.exists?(original)
|
||||
optimized = OptimizedImage.create_for(
|
||||
user.uploaded_avatar,
|
||||
upload,
|
||||
size,
|
||||
size,
|
||||
allow_animation: SiteSetting.allow_animated_avatars
|
|
@ -7,7 +7,7 @@ class UsersController < ApplicationController
|
|||
skip_before_filter :authorize_mini_profiler, only: [:avatar]
|
||||
skip_before_filter :check_xhr, only: [:show, :password_reset, :update, :activate_account, :authorize_email, :user_preferences_redirect, :avatar, :my_redirect]
|
||||
|
||||
before_filter :ensure_logged_in, only: [:username, :update, :change_email, :user_preferences_redirect, :upload_user_image, :toggle_avatar, :clear_profile_background, :destroy]
|
||||
before_filter :ensure_logged_in, only: [:username, :update, :change_email, :user_preferences_redirect, :upload_user_image, :pick_avatar, :clear_profile_background, :destroy]
|
||||
before_filter :respond_to_suspicious_request, only: [:create]
|
||||
|
||||
# we need to allow account creation with bad CSRF tokens, if people are caching, the CSRF token on the
|
||||
|
@ -364,12 +364,18 @@ class UsersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def toggle_avatar
|
||||
params.require(:use_uploaded_avatar)
|
||||
def pick_avatar
|
||||
params.require(:upload_id)
|
||||
user = fetch_user_from_params
|
||||
guardian.ensure_can_edit!(user)
|
||||
upload_id = params[:upload_id]
|
||||
|
||||
user.use_uploaded_avatar = params[:use_uploaded_avatar]
|
||||
user.uploaded_avatar_id = upload_id
|
||||
|
||||
# ensure we associate the custom avatar properly
|
||||
unless user.user_avatar.contains_upload?(upload_id)
|
||||
user.user_avatar.custom_upload_id = upload_id
|
||||
end
|
||||
user.save!
|
||||
|
||||
render nothing: true
|
||||
|
@ -421,8 +427,7 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
def upload_avatar_for(user, upload)
|
||||
user.upload_avatar(upload)
|
||||
render json: { url: upload.url, width: upload.width, height: upload.height }
|
||||
render json: { upload_id: upload.id, url: upload.url, width: upload.width, height: upload.height }
|
||||
end
|
||||
|
||||
def upload_profile_background_for(user, upload)
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
module Jobs
|
||||
class CreateMissingAvatars < Jobs::Base
|
||||
def execute(args)
|
||||
User.find_each do |u|
|
||||
u.refresh_avatar
|
||||
u.save
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,14 +6,15 @@ module Jobs
|
|||
def execute(args)
|
||||
return unless SiteSetting.clean_up_uploads?
|
||||
|
||||
uploads_used_in_posts = PostUpload.uniq.pluck(:upload_id)
|
||||
uploads_used_as_avatars = User.uniq.where('uploaded_avatar_id IS NOT NULL').pluck(:uploaded_avatar_id)
|
||||
uploads_used_as_profile_backgrounds = User.uniq.where("profile_background IS NOT NULL AND profile_background != ''").pluck(:profile_background)
|
||||
|
||||
grace_period = [SiteSetting.clean_orphan_uploads_grace_period_hours, 1].max
|
||||
|
||||
Upload.where("created_at < ?", grace_period.hour.ago)
|
||||
.where("id NOT IN (?)", uploads_used_in_posts + uploads_used_as_avatars)
|
||||
.where("id NOT IN (SELECT upload_id from post_uploads)")
|
||||
.where("id NOT IN (SELECT system_upload_id from post_uploads)")
|
||||
.where("id NOT IN (SELECT custom_upload_id from post_uploads)")
|
||||
.where("id NOT IN (SELECT gravatar_upload_id from post_uploads)")
|
||||
.where("url NOT IN (?)", uploads_used_as_profile_backgrounds)
|
||||
.find_each do |upload|
|
||||
upload.destroy
|
||||
|
|
15
app/jobs/scheduled/create_missing_avatars.rb
Normal file
15
app/jobs/scheduled/create_missing_avatars.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
module Jobs
|
||||
class CreateMissingAvatars < Jobs::Scheduled
|
||||
every 1.hour
|
||||
def execute(args)
|
||||
User.where(uploaded_avatar_id: nil).find_each do |u|
|
||||
u.refresh_avatar
|
||||
u.save
|
||||
end
|
||||
|
||||
UserAvatar.where(system_upload_id: nil).find_each do |a|
|
||||
a.update_system_avatar!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -354,7 +354,7 @@ class User < ActiveRecord::Base
|
|||
def self.avatar_template(username,uploaded_avatar_id)
|
||||
id = uploaded_avatar_id || -1
|
||||
username ||= ""
|
||||
"/avatar/#{username.downcase}/{size}/#{id}.png"
|
||||
"/user_avatar/#{username.downcase}/{size}/#{id}.png"
|
||||
end
|
||||
|
||||
def avatar_template
|
||||
|
@ -547,13 +547,9 @@ class User < ActiveRecord::Base
|
|||
created_at > 1.day.ago
|
||||
end
|
||||
|
||||
def upload_avatar(upload)
|
||||
self.uploaded_avatar_template = nil
|
||||
self.uploaded_avatar = upload
|
||||
self.use_uploaded_avatar = true
|
||||
self.save!
|
||||
end
|
||||
|
||||
# TODO this is a MESS
|
||||
# at most user table should have profile_background_upload_id
|
||||
# best case is to move this to another table
|
||||
def upload_profile_background(upload)
|
||||
self.profile_background = upload.url
|
||||
self.save!
|
||||
|
@ -631,7 +627,11 @@ class User < ActiveRecord::Base
|
|||
avatar.update_system_avatar! if !avatar.system_upload_id || username_changed?
|
||||
end
|
||||
|
||||
self.uploaded_avatar_id = (avatar.gravatar_upload_id || avatar.system_upload_id) unless uploaded_avatar_id
|
||||
desired_avatar_id = avatar.gravatar_upload_id || avatar.system_upload_id
|
||||
|
||||
if !self.uploaded_avatar_id && desired_avatar_id
|
||||
self.update_column(:uploaded_avatar_id, desired_avatar_id)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -789,7 +789,6 @@ end
|
|||
# dynamic_favicon :boolean default(FALSE), not null
|
||||
# title :string(255)
|
||||
# use_uploaded_avatar :boolean default(FALSE)
|
||||
# uploaded_avatar_template :string(255)
|
||||
# uploaded_avatar_id :integer
|
||||
# email_always :boolean default(FALSE), not null
|
||||
# mailing_list_mode :boolean default(FALSE), not null
|
||||
|
|
|
@ -27,10 +27,13 @@ class UserAvatar < ActiveRecord::Base
|
|||
|
||||
upload = Upload.create_for(user.id, tempfile, 'gravatar.png', File.size(tempfile.path))
|
||||
|
||||
gravatar_upload.destroy! if gravatar_upload
|
||||
if gravatar_upload_id != upload.id
|
||||
gravatar_upload.try(:destroy!)
|
||||
self.gravatar_upload = upload
|
||||
save!
|
||||
rescue OpenURI::HTTPError
|
||||
else
|
||||
gravatar_upload
|
||||
end
|
||||
save!
|
||||
ensure
|
||||
tempfile.unlink if tempfile
|
||||
|
|
|
@ -67,17 +67,4 @@ class UserActionSerializer < ApplicationSerializer
|
|||
object.action_type == UserAction::EDIT
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def avatar_for(user_id, email, use_uploaded_avatar, uploaded_avatar_template, uploaded_avatar_id)
|
||||
# NOTE: id is required for cases where the template is blank (during initial population)
|
||||
User.new(
|
||||
id: user_id,
|
||||
email: email,
|
||||
use_uploaded_avatar: use_uploaded_avatar,
|
||||
uploaded_avatar_template: uploaded_avatar_template,
|
||||
uploaded_avatar_id: uploaded_avatar_id
|
||||
).avatar_template
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -202,7 +202,7 @@ Discourse::Application.routes.draw do
|
|||
get "users/:username/avatar(/:size)" => "users#avatar", constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE
|
||||
post "users/:username/preferences/avatar" => "users#upload_avatar", constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE
|
||||
post "users/:username/preferences/user_image" => "users#upload_user_image", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put "users/:username/preferences/avatar/toggle" => "users#toggle_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put "users/:username/preferences/avatar/pick" => "users#pick_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put "users/:username/preferences/profile_background/clear" => "users#clear_profile_background", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
post "users/:username/send_activation_email" => "users#send_activation_email", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
|
@ -211,6 +211,10 @@ Discourse::Application.routes.draw do
|
|||
get "users/:username/badges" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
delete "users/:username" => "users#destroy", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
|
||||
post "user_avatar/:username/refresh_gravatar" => "user_avatars#refresh_gravatar"
|
||||
get "user_avatar/:username/:size/:version.png" => "user_avatars#show", format: false
|
||||
|
||||
|
||||
get "uploads/:site/:id/:sha.:extension" => "uploads#show", constraints: {site: /\w+/, id: /\d+/, sha: /[a-z0-9]{15,16}/i, extension: /\w{2,}/}
|
||||
post "uploads" => "uploads#create"
|
||||
|
||||
|
@ -366,8 +370,6 @@ Discourse::Application.routes.draw do
|
|||
post "draft" => "draft#update"
|
||||
delete "draft" => "draft#destroy"
|
||||
|
||||
get "avatar/:username/:size/:version.png" => "avatar#show", format: false
|
||||
|
||||
get "cdn_asset/:site/*path" => "static#cdn_asset", format: false
|
||||
|
||||
get "robots.txt" => "robots_txt#index"
|
||||
|
|
|
@ -23,8 +23,3 @@ User.seed do |u|
|
|||
u.email_private_messages = false
|
||||
u.trust_level = TrustLevel.levels[:elder]
|
||||
end
|
||||
|
||||
# download avatars for existing users
|
||||
if UserAvatar.count < User.count
|
||||
Jobs.enqueue(:create_missing_avatars)
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class AddUserAvatars < ActiveRecord::Migration
|
||||
def change
|
||||
def up
|
||||
create_table :user_avatars do |t|
|
||||
t.integer :user_id, null: false
|
||||
t.integer :system_upload_id
|
||||
|
@ -8,5 +8,17 @@ class AddUserAvatars < ActiveRecord::Migration
|
|||
t.datetime :last_gravatar_download_attempt
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :user_avatars, [:user_id]
|
||||
|
||||
execute <<SQL
|
||||
INSERT INTO user_avatars(user_id, custom_upload_id)
|
||||
SELECT id, uploaded_avatar_id
|
||||
FROM users
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :user_avatars
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class RemoveUploadedAvatarTemplateFromUsers < ActiveRecord::Migration
|
||||
def change
|
||||
remove_column :users, :uploaded_avatar_template
|
||||
end
|
||||
end
|
|
@ -89,7 +89,8 @@ describe CookedPostProcessor do
|
|||
# optimized_image
|
||||
FileUtils.stubs(:mkdir_p)
|
||||
File.stubs(:open)
|
||||
ImageSorcery.any_instance.expects(:convert).returns(true)
|
||||
# hmmm this should be done in a cleaner way
|
||||
OptimizedImage.any_instance.expects(:resize).returns(true)
|
||||
end
|
||||
|
||||
it "generates overlay information" do
|
||||
|
|
|
@ -1137,18 +1137,12 @@ describe UsersController do
|
|||
Upload.expects(:create_for).returns(upload)
|
||||
# enqueues the user_image generator job
|
||||
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "avatar"
|
||||
user.reload
|
||||
# erase the previous template
|
||||
user.uploaded_avatar_template.should == nil
|
||||
# link to the right upload
|
||||
user.uploaded_avatar.id.should == upload.id
|
||||
# automatically set "use_uploaded_user_image"
|
||||
user.use_uploaded_avatar.should == true
|
||||
# returns the url, width and height of the uploaded image
|
||||
json = JSON.parse(response.body)
|
||||
json['url'].should == "/uploads/default/1/1234567890123456.png"
|
||||
json['width'].should == 100
|
||||
json['height'].should == 200
|
||||
json['upload_id'].should == upload.id
|
||||
end
|
||||
|
||||
it 'is successful for profile backgrounds' do
|
||||
|
@ -1194,18 +1188,11 @@ describe UsersController do
|
|||
Upload.expects(:create_for).returns(upload)
|
||||
# enqueues the user_image generator job
|
||||
xhr :post, :upload_avatar, username: user.username, file: user_image_url, user_image_type: "avatar"
|
||||
user.reload
|
||||
# erase the previous template
|
||||
user.uploaded_avatar_template.should == nil
|
||||
# link to the right upload
|
||||
user.uploaded_avatar.id.should == upload.id
|
||||
# automatically set "use_uploaded_user_image"
|
||||
user.use_uploaded_avatar.should == true
|
||||
# returns the url, width and height of the uploaded image
|
||||
json = JSON.parse(response.body)
|
||||
json['url'].should == "/uploads/default/1/1234567890123456.png"
|
||||
json['width'].should == 100
|
||||
json['height'].should == 200
|
||||
json['upload_id'].should == upload.id
|
||||
end
|
||||
|
||||
it 'is successful for profile backgrounds' do
|
||||
|
@ -1234,29 +1221,29 @@ describe UsersController do
|
|||
|
||||
end
|
||||
|
||||
describe '.toggle_avatar' do
|
||||
describe '.pick_avatar' do
|
||||
|
||||
it 'raises an error when not logged in' do
|
||||
lambda { xhr :put, :toggle_avatar, username: 'asdf' }.should raise_error(Discourse::NotLoggedIn)
|
||||
lambda { xhr :put, :pick_avatar, username: 'asdf', avatar_id: 1}.should raise_error(Discourse::NotLoggedIn)
|
||||
end
|
||||
|
||||
context 'while logged in' do
|
||||
|
||||
let!(:user) { log_in }
|
||||
|
||||
it 'raises an error without a use_uploaded_avatar param' do
|
||||
lambda { xhr :put, :toggle_avatar, username: user.username }.should raise_error(ActionController::ParameterMissing)
|
||||
it 'raises an error without an avatar_id param' do
|
||||
lambda { xhr :put, :pick_avatar, username: user.username }.should raise_error(ActionController::ParameterMissing)
|
||||
end
|
||||
|
||||
it 'raises an error when you don\'t have permission to toggle the avatar' do
|
||||
Guardian.any_instance.expects(:can_edit?).with(user).returns(false)
|
||||
xhr :put, :toggle_avatar, username: user.username, use_uploaded_avatar: "true"
|
||||
another_user = Fabricate(:user)
|
||||
xhr :put, :pick_avatar, username: another_user.username, upload_id: 1
|
||||
response.should be_forbidden
|
||||
end
|
||||
|
||||
it 'it successful' do
|
||||
xhr :put, :toggle_avatar, username: user.username, use_uploaded_avatar: "false"
|
||||
user.reload.use_uploaded_avatar.should == false
|
||||
xhr :put, :pick_avatar, username: user.username, upload_id: 111
|
||||
user.reload.uploaded_avatar_id.should == 111
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
|
|
|
@ -15,11 +15,10 @@ describe OptimizedImage do
|
|||
let(:store) { FakeInternalStore.new }
|
||||
before { Discourse.stubs(:store).returns(store) }
|
||||
|
||||
context "when an error happened while generatign the thumbnail" do
|
||||
|
||||
before { ImageSorcery.any_instance.stubs(:convert).returns(false) }
|
||||
context "when an error happened while generating the thumbnail" do
|
||||
|
||||
it "returns nil" do
|
||||
OptimizedImage.expects(:resize).returns(false)
|
||||
OptimizedImage.create_for(upload, 100, 200).should be_nil
|
||||
end
|
||||
|
||||
|
@ -27,7 +26,9 @@ describe OptimizedImage do
|
|||
|
||||
context "when the thumbnail is properly generated" do
|
||||
|
||||
before { ImageSorcery.any_instance.stubs(:convert).returns(true) }
|
||||
before do
|
||||
OptimizedImage.expects(:resize).returns(true)
|
||||
end
|
||||
|
||||
it "does not download a copy of the original image" do
|
||||
store.expects(:download).never
|
||||
|
@ -59,9 +60,8 @@ describe OptimizedImage do
|
|||
|
||||
context "when an error happened while generatign the thumbnail" do
|
||||
|
||||
before { ImageSorcery.any_instance.stubs(:convert).returns(false) }
|
||||
|
||||
it "returns nil" do
|
||||
OptimizedImage.expects(:resize).returns(false)
|
||||
OptimizedImage.create_for(upload, 100, 200).should be_nil
|
||||
end
|
||||
|
||||
|
@ -69,7 +69,9 @@ describe OptimizedImage do
|
|||
|
||||
context "when the thumbnail is properly generated" do
|
||||
|
||||
before { ImageSorcery.any_instance.stubs(:convert).returns(true) }
|
||||
before do
|
||||
OptimizedImage.expects(:resize).returns(true)
|
||||
end
|
||||
|
||||
it "downloads a copy of the original image" do
|
||||
Tempfile.any_instance.expects(:close!).twice
|
||||
|
|
|
@ -854,15 +854,6 @@ describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#upload_avatar" do
|
||||
let(:upload) { Fabricate(:upload) }
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it "should update user's avatar" do
|
||||
expect(user.upload_avatar(upload)).to be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'api keys' do
|
||||
let(:admin) { Fabricate(:admin) }
|
||||
let(:other_admin) { Fabricate(:admin) }
|
||||
|
@ -987,7 +978,7 @@ describe User do
|
|||
let(:user) { build(:user, username: 'Sam') }
|
||||
|
||||
it "returns a 45-pixel-wide avatar" do
|
||||
user.small_avatar_url.should == "//test.localhost/avatar/sam/45/-1.png"
|
||||
user.small_avatar_url.should == "//test.localhost/user_avatar/sam/45/-1.png"
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -996,18 +987,13 @@ describe User do
|
|||
|
||||
let(:user) { build(:user, uploaded_avatar_id: 99, username: 'Sam') }
|
||||
|
||||
it "returns default when uploaded avatars are not allowed" do
|
||||
SiteSetting.allow_uploaded_avatars = false
|
||||
user.avatar_template_url.should == "//test.localhost/avatar/sam/{size}/-1.png"
|
||||
end
|
||||
|
||||
it "returns a schemaless avatar template with correct id" do
|
||||
user.avatar_template_url.should == "//test.localhost/avatar/sam/{size}/99.png"
|
||||
user.avatar_template_url.should == "//test.localhost/user_avatar/sam/{size}/99.png"
|
||||
end
|
||||
|
||||
it "returns a schemaless cdn-based avatar template" do
|
||||
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
||||
user.avatar_template_url.should == "//my.cdn.com/avatar/sam/{size}/99.png"
|
||||
user.avatar_template_url.should == "//my.cdn.com/user_avatar/sam/{size}/99.png"
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1148,6 +1134,16 @@ describe User do
|
|||
|
||||
end
|
||||
|
||||
describe "automatic avatar creation" do
|
||||
it "sets a system avatar for new users" do
|
||||
SiteSetting.enable_system_avatars = true
|
||||
u = User.create!(username: "bob", email: "bob@bob.com")
|
||||
u.reload
|
||||
u.user_avatar.system_upload_id.should == u.uploaded_avatar_id
|
||||
u.uploaded_avatar_id.should_not == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "custom fields" do
|
||||
it "allows modification of custom fields" do
|
||||
user = Fabricate(:user)
|
||||
|
|
Loading…
Reference in a new issue