Work in progress, keeping avatars locally
This introduces a new model to store the avatars and 3 uploads per user (gravatar, system and custom) user can then pick which they want.
This commit is contained in:
parent
4ccf07be8c
commit
6c1c8be794
|
@ -10,12 +10,14 @@
|
|||
export default Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
||||
|
||||
actions: {
|
||||
useUploadedAvatar: function() { this.set("use_uploaded_avatar", true); },
|
||||
useGravatar: function() { this.set("use_uploaded_avatar", false); }
|
||||
},
|
||||
|
||||
avatarTemplate: function() {
|
||||
return this.get("use_uploaded_avatar") ? this.get("uploaded_avatar_template") : this.get("gravatar_template");
|
||||
}.property("use_uploaded_avatar", "uploaded_avatar_template", "gravatar_template")
|
||||
|
||||
useUploadedAvatar: function() {
|
||||
this.set("selected", "uploaded");
|
||||
},
|
||||
useGravatar: function() {
|
||||
this.set("selected", "gravatar");
|
||||
},
|
||||
useSystem: function() {
|
||||
this.set("selected", "system");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -183,16 +183,6 @@ Handlebars.registerHelper('avatar', function(user, options) {
|
|||
var username = Em.get(user, 'username');
|
||||
if (!username) username = Em.get(user, options.hash.usernamePath);
|
||||
|
||||
var avatarTemplate;
|
||||
var template = options.hash.template;
|
||||
if (template && template !== 'avatar_template') {
|
||||
avatarTemplate = Em.get(user, template);
|
||||
if (!avatarTemplate) avatarTemplate = Em.get(user, 'user.' + template);
|
||||
}
|
||||
|
||||
if (!avatarTemplate) avatarTemplate = Em.get(user, 'avatar_template');
|
||||
if (!avatarTemplate) avatarTemplate = Em.get(user, 'user.avatar_template');
|
||||
|
||||
var title;
|
||||
if (!options.hash.ignoreTitle) {
|
||||
// first try to get a title
|
||||
|
@ -209,6 +199,10 @@ Handlebars.registerHelper('avatar', function(user, options) {
|
|||
}
|
||||
}
|
||||
|
||||
// this is simply done to ensure we cache images correctly
|
||||
var uploadedAvatarId = Em.get(user, 'uploaded_avatar_id') || Em.get(user, 'user.uploaded_avatar_id') || "_1";
|
||||
var avatarTemplate = Discourse.User.avatarTemplate(username,uploadedAvatarId);
|
||||
|
||||
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
|
||||
size: options.hash.imageSize,
|
||||
extraClasses: Em.get(user, 'extras') || options.hash.extraClasses,
|
||||
|
@ -228,11 +222,19 @@ Handlebars.registerHelper('avatar', function(user, options) {
|
|||
@for Handlebars
|
||||
**/
|
||||
Ember.Handlebars.registerBoundHelper('boundAvatar', function(user, options) {
|
||||
|
||||
var username = Em.get(user, 'username');
|
||||
|
||||
console.log(options.hash);
|
||||
|
||||
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,
|
||||
avatarTemplate: Em.get(user, options.hash.template || 'avatar_template')
|
||||
avatarTemplate: avatarTemplate
|
||||
}));
|
||||
}, 'avatar_template', 'uploaded_avatar_template', 'gravatar_template');
|
||||
}, 'uploadId', 'username', 'uploaded_avatar_id');
|
||||
|
||||
/**
|
||||
Nicely format a date without binding or returning HTML
|
||||
|
|
|
@ -326,6 +326,10 @@ Discourse.User = Discourse.Model.extend({
|
|||
});
|
||||
},
|
||||
|
||||
avatarTemplate: function(){
|
||||
return Discourse.User.avatarTemplate(this.get('username'),this.get('uploaded_avatar_id'));
|
||||
}.property('uploaded_avatar_id', 'username'),
|
||||
|
||||
/*
|
||||
Change avatar selection
|
||||
|
||||
|
@ -413,6 +417,11 @@ Discourse.User = Discourse.Model.extend({
|
|||
});
|
||||
|
||||
Discourse.User.reopenClass(Discourse.Singleton, {
|
||||
|
||||
avatarTemplate: function(username, uploadedAvatarId){
|
||||
return Discourse.getURL("/avatar/" + username.toLowerCase() + "/{size}/" + uploadedAvatarId + ".png");
|
||||
},
|
||||
|
||||
/**
|
||||
Find a `Discourse.User` for a given username.
|
||||
|
||||
|
|
|
@ -184,7 +184,7 @@ Discourse.UserAction = Discourse.Model.extend({
|
|||
switchToActing: function() {
|
||||
this.setProperties({
|
||||
username: this.get('acting_username'),
|
||||
avatar_template: this.get('acting_avatar_template'),
|
||||
uploaded_avatar_id: this.get('acting_uploaded_avatar_id'),
|
||||
name: this.get('actingDisplayName')
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,8 +22,11 @@ Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
|
|||
// all the properties needed for displaying the avatar selector modal
|
||||
this.controllerFor('avatar-selector').setProperties(this.modelFor('user').getProperties(
|
||||
'username', 'email',
|
||||
'has_uploaded_avatar', 'use_uploaded_avatar',
|
||||
'gravatar_template', 'uploaded_avatar_template'));
|
||||
'system_avatar_upload_id',
|
||||
'gravatr_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
saveAvatarSelection: function() {
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
<div class="modal-body">
|
||||
<div>
|
||||
<div>
|
||||
<input type="radio" id="avatar" name="avatar" value="gravatar" {{action useGravatar}}>
|
||||
<label class="radio" for="avatar">{{avatar controller imageSize="large" template="gravatar_template"}} {{{i18n user.change_avatar.gravatar}}} {{email}}</label>
|
||||
<a href="//gravatar.com/emails" target="_blank" title="{{i18n user.change_avatar.gravatar_title}}" class="btn no-text"><i class="fa fa-pencil"></i></a>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded_avatar" {{action useUploadedAvatar}}>
|
||||
<label class="radio" for="uploaded_avatar">
|
||||
{{#if has_uploaded_avatar}}
|
||||
{{boundAvatar controller imageSize="large" template="uploaded_avatar_template"}} {{i18n user.change_avatar.uploaded_avatar}}
|
||||
{{#if custom_avatar_upload_id}}
|
||||
{{boundAvatar controller imageSize="large" uploadId="custom_avatar_upload_id"}} {{i18n user.change_avatar.uploaded_avatar}}
|
||||
{{else}}
|
||||
{{i18n user.change_avatar.uploaded_avatar_empty}}
|
||||
{{/if}}
|
||||
|
|
|
@ -105,7 +105,7 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
|
|||
}.observes('controller.use_uploaded_avatar'),
|
||||
|
||||
uploadButtonText: function() {
|
||||
return this.get("uploading") ? I18n.t("uploading") : I18n.t("upload");
|
||||
return this.get("uploading") ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture");
|
||||
}.property("uploading")
|
||||
|
||||
});
|
||||
|
|
|
@ -182,3 +182,11 @@ animation: modal .25s;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-options {
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.uploaded-avatar {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
require_dependency 'letter_avatar'
|
||||
class AvatarController < ApplicationController
|
||||
|
||||
skip_before_filter :check_xhr, :verify_authenticity_token
|
||||
|
||||
def show
|
||||
username = params[:username].to_s
|
||||
raise Discourse::NotFound unless user = User.find_by(username_lower: username.downcase)
|
||||
|
||||
size = params[:size].to_i
|
||||
if size > 1000 || size < 1
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
|
||||
image = nil
|
||||
version = params[:version].to_i
|
||||
|
||||
raise Discourse::NotFound unless version > 0 && user_avatar = user.user_avatar
|
||||
|
||||
upload = 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)
|
||||
if File.exists?(original)
|
||||
optimized = OptimizedImage.create_for(
|
||||
user.uploaded_avatar,
|
||||
size,
|
||||
size,
|
||||
allow_animation: SiteSetting.allow_animated_avatars
|
||||
)
|
||||
image = Discourse.store.path_for(optimized)
|
||||
end
|
||||
end
|
||||
|
||||
if image
|
||||
expires_in 1.year, public: true
|
||||
send_file image, disposition: nil
|
||||
else
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
end
|
||||
end
|
|
@ -422,8 +422,6 @@ class UsersController < ApplicationController
|
|||
|
||||
def upload_avatar_for(user, upload)
|
||||
user.upload_avatar(upload)
|
||||
Jobs.enqueue(:generate_avatars, user_id: user.id, upload_id: upload.id)
|
||||
|
||||
render json: { url: upload.url, width: upload.width, height: upload.height }
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
module Jobs
|
||||
class CreateMissingAvatars < Jobs::Base
|
||||
def execute(args)
|
||||
User.find_each do |u|
|
||||
u.refresh_avatar
|
||||
u.save
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,67 +0,0 @@
|
|||
require "image_sorcery"
|
||||
|
||||
module Jobs
|
||||
|
||||
class GenerateAvatars < Jobs::Base
|
||||
|
||||
def execute(args)
|
||||
raise Discourse::ImageMagickMissing.new unless system("command -v convert >/dev/null;")
|
||||
|
||||
upload_id, user_id = args[:upload_id], args[:user_id]
|
||||
raise Discourse::InvalidParameters.new(:upload_id) if upload_id.blank?
|
||||
raise Discourse::InvalidParameters.new(:user_id) if user_id.blank?
|
||||
|
||||
upload = Upload.find_by(id: upload_id)
|
||||
user = User.find_by(id: user_id)
|
||||
return if upload.nil? || user.nil?
|
||||
|
||||
external_copy = Discourse.store.download(upload) if Discourse.store.external?
|
||||
original_path = if Discourse.store.external?
|
||||
external_copy.path
|
||||
else
|
||||
Discourse.store.path_for(upload)
|
||||
end
|
||||
|
||||
source = original_path
|
||||
# extract the first frame when it's a gif
|
||||
source << "[0]" unless SiteSetting.allow_animated_avatars
|
||||
image = ImageSorcery.new(source)
|
||||
extension = File.extname(original_path)
|
||||
|
||||
[120, 45, 32, 25, 20].each do |s|
|
||||
# handle retina too
|
||||
[s, s * 2].each do |size|
|
||||
begin
|
||||
# create a temp file with the same extension as the original
|
||||
temp_file = Tempfile.new(["discourse-avatar", extension])
|
||||
# create a transparent centered square thumbnail
|
||||
if image.convert(temp_file.path,
|
||||
gravity: "center",
|
||||
thumbnail: "#{size}x#{size}^",
|
||||
extent: "#{size}x#{size}",
|
||||
background: "transparent")
|
||||
if Discourse.store.store_avatar(temp_file, upload, size).blank?
|
||||
Rails.logger.error("Failed to store avatar #{size} for #{upload.url} from #{source}")
|
||||
end
|
||||
else
|
||||
Rails.logger.error("Failed to create avatar #{size} for #{upload.url} from #{source}")
|
||||
end
|
||||
ensure
|
||||
# close && remove temp file
|
||||
temp_file && temp_file.close!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# make sure we remove the cached copy from external stores
|
||||
external_copy.close! if Discourse.store.external?
|
||||
|
||||
# attach the avatar to the user
|
||||
user.uploaded_avatar_template = Discourse.store.avatar_template(upload)
|
||||
user.save!
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,31 +0,0 @@
|
|||
require_dependency 'avatar_detector'
|
||||
|
||||
module Jobs
|
||||
|
||||
class DetectAvatars < Jobs::Scheduled
|
||||
every 8.hours
|
||||
|
||||
def execute(args)
|
||||
return unless SiteSetting.detect_custom_avatars?
|
||||
|
||||
# Find a random sampling of users of trust level 1 or higher who don't have a custom avatar.
|
||||
user_stats = UserStat.where('user_stats.has_custom_avatar = false AND users.trust_level > 0')
|
||||
.references(:user)
|
||||
.includes(:user)
|
||||
.order("random()")
|
||||
.limit(SiteSetting.max_daily_gravatar_crawls)
|
||||
|
||||
if user_stats.present?
|
||||
user_stats.each do |us|
|
||||
us.update_column(:has_custom_avatar, true) if AvatarDetector.new(us.user).has_custom_avatar?
|
||||
UserHistory.create!(
|
||||
action: UserHistory.actions[:checked_for_custom_avatar],
|
||||
target_user_id: us.user_id
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -33,6 +33,7 @@ class User < ActiveRecord::Base
|
|||
has_many :topic_links, dependent: :destroy
|
||||
has_many :uploads
|
||||
|
||||
has_one :user_avatar, dependent: :destroy
|
||||
has_one :facebook_user_info, dependent: :destroy
|
||||
has_one :twitter_user_info, dependent: :destroy
|
||||
has_one :github_user_info, dependent: :destroy
|
||||
|
@ -49,7 +50,7 @@ class User < ActiveRecord::Base
|
|||
has_one :user_search_data, dependent: :destroy
|
||||
has_one :api_key, dependent: :destroy
|
||||
|
||||
belongs_to :uploaded_avatar, class_name: 'Upload', dependent: :destroy
|
||||
belongs_to :uploaded_avatar, class_name: 'Upload'
|
||||
|
||||
delegate :last_sent_email_address, :to => :email_logs
|
||||
|
||||
|
@ -72,6 +73,7 @@ class User < ActiveRecord::Base
|
|||
|
||||
after_create :create_email_token
|
||||
after_create :create_user_stat
|
||||
after_save :refresh_avatar
|
||||
|
||||
before_destroy do
|
||||
# These tables don't have primary keys, so destroying them with activerecord is tricky:
|
||||
|
@ -336,25 +338,27 @@ class User < ActiveRecord::Base
|
|||
"//www.gravatar.com/avatar/#{email_hash}.png?s={size}&r=pg&d=identicon"
|
||||
end
|
||||
|
||||
|
||||
# Don't pass this up to the client - it's meant for server side use
|
||||
# This is used in
|
||||
# - self oneboxes in open graph data
|
||||
# - emails
|
||||
def small_avatar_url
|
||||
template = avatar_template
|
||||
schemaless template.gsub("{size}", "45")
|
||||
avatar_template_url.gsub("{size}", "45")
|
||||
end
|
||||
|
||||
# the avatars might take a while to generate
|
||||
# so return the url of the original image in the meantime
|
||||
def uploaded_avatar_path
|
||||
return unless SiteSetting.allow_uploaded_avatars? && use_uploaded_avatar
|
||||
avatar_template = uploaded_avatar_template.present? ? uploaded_avatar_template : uploaded_avatar.try(:url)
|
||||
def avatar_template_url
|
||||
schemaless absolute avatar_template
|
||||
end
|
||||
|
||||
def self.avatar_template(username,uploaded_avatar_id)
|
||||
id = uploaded_avatar_id || -1
|
||||
username ||= ""
|
||||
"/avatar/#{username.downcase}/{size}/#{id}.png"
|
||||
end
|
||||
|
||||
def avatar_template
|
||||
uploaded_avatar_path || User.gravatar_template(id != -1 ? email : "team@discourse.org")
|
||||
self.class.avatar_template(username,uploaded_avatar_id)
|
||||
end
|
||||
|
||||
# The following count methods are somewhat slow - definitely don't use them in a loop.
|
||||
|
@ -616,6 +620,20 @@ class User < ActiveRecord::Base
|
|||
Jobs.enqueue_in(delay / 2, :update_top_redirection, user_id: self.id, redirected_at: Time.zone.now)
|
||||
end
|
||||
|
||||
def refresh_avatar
|
||||
avatar = user_avatar || UserAvatar.create!(user_id: id)
|
||||
|
||||
if SiteSetting.automatically_download_gravatars?
|
||||
avatar.update_gravatar! unless avatar.last_gravatar_download_attempt
|
||||
end
|
||||
|
||||
if SiteSetting.enable_system_avatars?
|
||||
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
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def cook
|
||||
|
@ -778,7 +796,6 @@ end
|
|||
# primary_group_id :integer
|
||||
# locale :string(10)
|
||||
# profile_background :string(255)
|
||||
# email_hash :string(255)
|
||||
# registration_ip_address :inet
|
||||
# last_redirected_to_top_at :datetime
|
||||
#
|
||||
|
|
|
@ -109,9 +109,9 @@ SELECT
|
|||
coalesce(p.post_number, 1) post_number,
|
||||
p.reply_to_post_number,
|
||||
pu.email, pu.username, pu.name, pu.id user_id,
|
||||
pu.use_uploaded_avatar, pu.uploaded_avatar_template, pu.uploaded_avatar_id,
|
||||
pu.uploaded_avatar_id,
|
||||
u.email acting_email, u.username acting_username, u.name acting_name, u.id acting_user_id,
|
||||
u.use_uploaded_avatar acting_use_uploaded_avatar, u.uploaded_avatar_template acting_uploaded_avatar_template, u.uploaded_avatar_id acting_uploaded_avatar_id,
|
||||
u.uploaded_avatar_id acting_uploaded_avatar_id,
|
||||
coalesce(p.cooked, p2.cooked) cooked,
|
||||
CASE WHEN coalesce(p.deleted_at, p2.deleted_at, t.deleted_at) IS NULL THEN false ELSE true END deleted,
|
||||
p.hidden,
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
require_dependency 'letter_avatar'
|
||||
|
||||
class UserAvatar < ActiveRecord::Base
|
||||
MAX_SIZE = 240
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :system_upload, class_name: 'Upload', dependent: :destroy
|
||||
belongs_to :gravatar_upload, class_name: 'Upload', dependent: :destroy
|
||||
belongs_to :custom_upload, class_name: 'Upload', dependent: :destroy
|
||||
|
||||
def contains_upload?(id)
|
||||
system_upload_id == id || gravatar_upload_id == id || custom_upload_id == id
|
||||
end
|
||||
|
||||
# updates the letter based avatar
|
||||
def update_system_avatar!
|
||||
system_upload.destroy! if system_upload
|
||||
file = File.open(LetterAvatar.generate(user.username, MAX_SIZE), "r")
|
||||
self.system_upload = Upload.create_for(user_id, file, "avatar.png", file.size)
|
||||
save!
|
||||
end
|
||||
|
||||
def update_gravatar!
|
||||
self.last_gravatar_download_attempt = Time.new
|
||||
gravatar_url = "http://www.gravatar.com/avatar/#{user.email_hash}.png?s=500&d=404"
|
||||
tempfile = FileHelper.download(gravatar_url, 1.megabyte, "gravatar")
|
||||
|
||||
upload = Upload.create_for(user.id, tempfile, 'gravatar.png', File.size(tempfile.path))
|
||||
|
||||
gravatar_upload.destroy! if gravatar_upload
|
||||
self.gravatar_upload = upload
|
||||
save!
|
||||
rescue OpenURI::HTTPError
|
||||
save!
|
||||
ensure
|
||||
tempfile.unlink if tempfile
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: user_avatars
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer not null
|
||||
# system_upload_id :integer
|
||||
# custom_upload_id :integer
|
||||
# gravatar_upload_id :integer
|
||||
# last_gravatar_download_attempt :datetime
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
#
|
|
@ -85,7 +85,6 @@ end
|
|||
# Table name: user_stats
|
||||
#
|
||||
# user_id :integer not null, primary key
|
||||
# has_custom_avatar :boolean default(FALSE), not null
|
||||
# topics_entered :integer default(0), not null
|
||||
# time_read :integer default(0), not null
|
||||
# days_visited :integer default(0), not null
|
||||
|
|
|
@ -4,6 +4,7 @@ class BasicPostSerializer < ApplicationSerializer
|
|||
:name,
|
||||
:username,
|
||||
:avatar_template,
|
||||
:uploaded_avatar_id,
|
||||
:created_at,
|
||||
:cooked
|
||||
|
||||
|
@ -19,6 +20,10 @@ class BasicPostSerializer < ApplicationSerializer
|
|||
object.user.try(:avatar_template)
|
||||
end
|
||||
|
||||
def uploaded_avatar_id
|
||||
object.user.try(:uploaded_avatar_id)
|
||||
end
|
||||
|
||||
def cooked
|
||||
if object.hidden && !scope.is_staff?
|
||||
if scope.current_user && object.user_id == scope.current_user.id
|
||||
|
|
|
@ -1,7 +1,26 @@
|
|||
class BasicUserSerializer < ApplicationSerializer
|
||||
attributes :id, :username, :avatar_template
|
||||
attributes :id, :username, :uploaded_avatar_id, :avatar_template
|
||||
|
||||
def include_name?
|
||||
SiteSetting.enable_names?
|
||||
end
|
||||
|
||||
# so weird we send a hash in here sometimes and an object others
|
||||
def include_uploaded_avatar_id?
|
||||
SiteSetting.allow_uploaded_avatars? &&
|
||||
(Hash === object ? user[:uploaded_avatar_id] : object.uploaded_avatar_id)
|
||||
end
|
||||
|
||||
def avatar_template
|
||||
if Hash === object
|
||||
User.avatar_template(user[:username], user[:uploaded_avatar_id])
|
||||
else
|
||||
object.avatar_template
|
||||
end
|
||||
end
|
||||
|
||||
def user
|
||||
object[:user] || object
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -127,7 +127,8 @@ class PostSerializer < BasicPostSerializer
|
|||
def reply_to_user
|
||||
{
|
||||
username: object.reply_to_user.username,
|
||||
avatar_template: object.reply_to_user.avatar_template
|
||||
avatar_template: object.reply_to_user.avatar_template,
|
||||
uploaded_avatar_id: object.reply_to_user.uploaded_avatar_id
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -10,13 +10,15 @@ class TopicPostCountSerializer < BasicUserSerializer
|
|||
object[:user].username
|
||||
end
|
||||
|
||||
def avatar_template
|
||||
object[:user].avatar_template
|
||||
end
|
||||
|
||||
def post_count
|
||||
object[:post_count]
|
||||
end
|
||||
|
||||
def uploaded_avatar_id
|
||||
object[:user].uploaded_avatar_id
|
||||
end
|
||||
|
||||
def include_uploaded_avatar_id?
|
||||
SiteSetting.allow_uploaded_avatars? && object[:user].use_uploaded_avatar
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,30 +23,20 @@ class UserActionSerializer < ApplicationSerializer
|
|||
:hidden,
|
||||
:moderator_action,
|
||||
:edit_reason,
|
||||
:category_id
|
||||
:category_id,
|
||||
:uploaded_avatar_id,
|
||||
:acting_uploaded_avatar_id
|
||||
|
||||
def excerpt
|
||||
PrettyText.excerpt(object.cooked, 300) if object.cooked
|
||||
end
|
||||
|
||||
def avatar_template
|
||||
avatar_for(
|
||||
object.user_id,
|
||||
object.email,
|
||||
object.use_uploaded_avatar,
|
||||
object.uploaded_avatar_template,
|
||||
object.uploaded_avatar_id
|
||||
)
|
||||
User.avatar_template(object.username, object.uploaded_avatar_id)
|
||||
end
|
||||
|
||||
def acting_avatar_template
|
||||
avatar_for(
|
||||
object.acting_user_id,
|
||||
object.acting_email,
|
||||
object.acting_use_uploaded_avatar,
|
||||
object.acting_uploaded_avatar_template,
|
||||
object.acting_uploaded_avatar_id
|
||||
)
|
||||
User.avatar_template(object.acting_username, object.acting_uploaded_avatar_id)
|
||||
end
|
||||
|
||||
def include_name?
|
||||
|
|
|
@ -63,16 +63,27 @@ class UserSerializer < BasicUserSerializer
|
|||
:external_links_in_new_tab,
|
||||
:dynamic_favicon,
|
||||
:enable_quoting,
|
||||
:use_uploaded_avatar,
|
||||
:has_uploaded_avatar,
|
||||
:gravatar_template,
|
||||
:uploaded_avatar_template,
|
||||
:muted_category_ids,
|
||||
:tracked_category_ids,
|
||||
:watched_category_ids,
|
||||
:private_messages_stats,
|
||||
:disable_jump_reply
|
||||
:disable_jump_reply,
|
||||
:system_avatar_upload_id,
|
||||
:gravatar_avatar_upload_id,
|
||||
:custom_avatar_upload_id,
|
||||
:uploaded_avatar_id
|
||||
|
||||
def system_avatar_upload_id
|
||||
object.user_avatar.try(:system_upload_id)
|
||||
end
|
||||
|
||||
def gravatar_avatar_upload_id
|
||||
object.user_avatar.try(:gravatar_upload_id)
|
||||
end
|
||||
|
||||
def custom_avatar_upload_id
|
||||
object.user_avatar.try(:custom_upload_id)
|
||||
end
|
||||
|
||||
def auto_track_topics_after_msecs
|
||||
object.auto_track_topics_after_msecs || SiteSetting.auto_track_topics_after
|
||||
|
@ -106,10 +117,6 @@ class UserSerializer < BasicUserSerializer
|
|||
UserAction.stats(object.id, scope)
|
||||
end
|
||||
|
||||
def gravatar_template
|
||||
User.gravatar_template(object.email)
|
||||
end
|
||||
|
||||
def include_suspended?
|
||||
object.suspended?
|
||||
end
|
||||
|
|
|
@ -22,6 +22,7 @@ if defined?(Rack::MiniProfiler)
|
|||
(path !~ /^\/message-bus/) &&
|
||||
(path !~ /topics\/timings/) &&
|
||||
(path !~ /assets/) &&
|
||||
(path !~ /\/avatar\//) &&
|
||||
(path !~ /qunit/) &&
|
||||
(path !~ /srv\/status/) &&
|
||||
(path !~ /commits-widget/) &&
|
||||
|
|
|
@ -307,10 +307,12 @@ en:
|
|||
change_avatar:
|
||||
title: "Change your avatar"
|
||||
gravatar: "<a href='//gravatar.com/emails' target='_blank'>Gravatar</a>, based on"
|
||||
gravatar_title: "Change your avatar on Gravatar's website"
|
||||
refresh_gravatar_title: "Refresh your Gravatar"
|
||||
letter_based: "System assigned avatar"
|
||||
uploaded_avatar: "Custom picture"
|
||||
uploaded_avatar_empty: "Add a custom picture"
|
||||
upload_title: "Upload your picture"
|
||||
upload_picture: "Upload Picture"
|
||||
image_is_not_a_square: "Warning: we've cropped your image; it is not square."
|
||||
|
||||
change_profile_background:
|
||||
|
|
|
@ -863,6 +863,7 @@ en:
|
|||
|
||||
allow_uploaded_avatars: "Allow users to upload their custom avatars"
|
||||
allow_animated_avatars: "Allow users to use animated gif for avatars. WARNING: it is highly recommended to run the avatars:regenerate rake task after changing that setting."
|
||||
automatically_download_gravatars: "Download gravatars for users upon account creation or email change"
|
||||
digest_topics: "The maximum amount of topics to display in an email digest"
|
||||
digest_min_excerpt_length: "How many characters we're aiming for for each post in the email digest"
|
||||
default_digest_email_frequency: "How often users receive digest emails by default. They can change this setting in their preferences."
|
||||
|
|
|
@ -366,6 +366,8 @@ 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"
|
||||
|
|
|
@ -329,14 +329,13 @@ files:
|
|||
allow_profile_backgrounds:
|
||||
client: true
|
||||
default: true
|
||||
automatically_download_gravatars: true
|
||||
allow_uploaded_avatars:
|
||||
client: true
|
||||
default: true
|
||||
allow_animated_avatars:
|
||||
client: true
|
||||
default: false
|
||||
detect_custom_avatars: true
|
||||
max_daily_gravatar_crawls: 500
|
||||
|
||||
trust:
|
||||
default_trust_level: 0
|
||||
|
@ -547,7 +546,11 @@ uncategorized:
|
|||
default: ''
|
||||
|
||||
enable_cdn_js_debugging: true
|
||||
|
||||
show_create_topics_notice:
|
||||
client: true
|
||||
default: true
|
||||
|
||||
enable_system_avatars:
|
||||
hidden: true
|
||||
default: true
|
||||
|
|
|
@ -23,3 +23,8 @@ 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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class RemoveHasCustomAvatarFromUserStats < ActiveRecord::Migration
|
||||
def change
|
||||
remove_column :user_stats, :has_custom_avatar
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
class AddUserAvatars < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :user_avatars do |t|
|
||||
t.integer :user_id, null: false
|
||||
t.integer :system_upload_id
|
||||
t.integer :custom_upload_id
|
||||
t.integer :gravatar_upload_id
|
||||
t.datetime :last_gravatar_download_attempt
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,32 +0,0 @@
|
|||
require_dependency 'user'
|
||||
require 'net/http'
|
||||
|
||||
class AvatarDetector
|
||||
|
||||
def initialize(user)
|
||||
raise "Tried to detect an avatar on a non-user instance" unless user && user.is_a?(User)
|
||||
|
||||
@user = user
|
||||
end
|
||||
|
||||
def has_custom_avatar?
|
||||
return true if @user.uploaded_avatar_path
|
||||
has_custom_gravatar?
|
||||
end
|
||||
|
||||
# Check whether the user has a gravatar by performing a HTTP HEAD request to
|
||||
# Gravatar using the `d=404` parameter.
|
||||
def has_custom_gravatar?
|
||||
result = Net::HTTP.start('www.gravatar.com') do |http|
|
||||
http.open_timeout = 2
|
||||
http.read_timeout = 2
|
||||
http.head("/avatar/#{User.email_hash(@user.email)}?d=404")
|
||||
end
|
||||
|
||||
return result.code.to_i == 200
|
||||
rescue
|
||||
# If the HTTP request fails, assume no gravatar
|
||||
false
|
||||
end
|
||||
|
||||
end
|
|
@ -15,8 +15,6 @@ class AvatarLookup
|
|||
@lookup_columns ||= [:id,
|
||||
:email,
|
||||
:username,
|
||||
:use_uploaded_avatar,
|
||||
:uploaded_avatar_template,
|
||||
:uploaded_avatar_id]
|
||||
end
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class ComposerMessagesFinder
|
|||
return unless @user.has_trust_level?(:basic)
|
||||
|
||||
# We don't notify users who have avatars or who have been notified already.
|
||||
return if @user.user_stat.has_custom_avatar? || UserHistory.exists_for_user?(@user, :notified_about_avatar)
|
||||
return if @user.uploaded_avatar_id || UserHistory.exists_for_user?(@user, :notified_about_avatar)
|
||||
|
||||
# Finally, we don't check users whose avatars haven't been examined
|
||||
return unless UserHistory.exists_for_user?(@user, :checked_for_custom_avatar)
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
class LetterAvatar
|
||||
class<<self
|
||||
|
||||
FULLSIZE = 240
|
||||
|
||||
def cache_path
|
||||
'tmp/letter_avatars'
|
||||
end
|
||||
|
||||
def generate(username, size)
|
||||
|
||||
size = FULLSIZE if size > FULLSIZE
|
||||
filename = cached_path(username, size)
|
||||
|
||||
if File.exists?(filename)
|
||||
return filename
|
||||
end
|
||||
|
||||
fullsize = fullsize_path(username)
|
||||
if !File.exists?(fullsize)
|
||||
generate_fullsize(username)
|
||||
end
|
||||
|
||||
resize(fullsize, filename, size)
|
||||
end
|
||||
|
||||
def cached_path(username, size)
|
||||
dir = "#{cache_path}/#{username}"
|
||||
FileUtils.mkdir_p(dir)
|
||||
|
||||
"#{dir}/#{size}.png"
|
||||
end
|
||||
|
||||
def fullsize_path(username)
|
||||
cached_path(username, FULLSIZE)
|
||||
end
|
||||
|
||||
def resize(from, to, size)
|
||||
`convert #{from} -resize #{size}x#{size} #{to}`
|
||||
to
|
||||
end
|
||||
|
||||
def generate_fullsize(username)
|
||||
|
||||
filename = fullsize_path(username)
|
||||
color = colors[Digest::MD5.hexdigest(username)[0...15].to_i(16) % 216]
|
||||
stroke = darken(color, 0.8)
|
||||
|
||||
instructions = %W{
|
||||
-size 240x240
|
||||
xc:#{to_rgb(color)}
|
||||
-pointsize 240
|
||||
-fill white
|
||||
-gravity Center
|
||||
-font 'Helvetica-Bold'
|
||||
-stroke #{to_rgb(stroke)}
|
||||
-strokewidth 2
|
||||
-annotate -5+25 '#{username[0].upcase}'
|
||||
'#{filename}'
|
||||
}
|
||||
|
||||
`convert #{instructions.join(" ")}`
|
||||
|
||||
filename
|
||||
end
|
||||
|
||||
def darken(color,pct)
|
||||
color.map do |n|
|
||||
(n.to_f * pct).to_i
|
||||
end
|
||||
end
|
||||
|
||||
def to_rgb(color)
|
||||
r,g,b = color
|
||||
"'rgb(#{r},#{g},#{b})'"
|
||||
end
|
||||
|
||||
# palette of 216 optimally disctinct colors
|
||||
# cf. http://tools.medialab.sciences-po.fr/iwanthue/index.php
|
||||
# parameters used:
|
||||
# - H: 0 - 360
|
||||
# - C: 0 - 2
|
||||
# - L: 0.75 - 1.5
|
||||
def colors
|
||||
[[198,125,40],
|
||||
[61,155,243],
|
||||
[74,243,75],
|
||||
[238,89,166],
|
||||
[52,240,224],
|
||||
[177,156,155],
|
||||
[247,242,53],
|
||||
[111,154,78],
|
||||
[237,179,245],
|
||||
[237,101,95],
|
||||
[89,239,155],
|
||||
[242,233,158],
|
||||
[163,212,245],
|
||||
[65,152,142],
|
||||
[165,135,246],
|
||||
[181,166,38],
|
||||
[187,229,206],
|
||||
[77,164,25],
|
||||
[179,246,101],
|
||||
[234,93,37],
|
||||
[225,155,115],
|
||||
[142,140,188],
|
||||
[223,120,140],
|
||||
[249,174,27],
|
||||
[244,117,225],
|
||||
[137,141,102],
|
||||
[75,191,146],
|
||||
[188,239,142],
|
||||
[164,199,145],
|
||||
[173,120,149],
|
||||
[59,195,89],
|
||||
[222,198,220],
|
||||
[68,145,187],
|
||||
[236,204,179],
|
||||
[159,195,72],
|
||||
[188,121,189],
|
||||
[166,160,85],
|
||||
[181,233,37],
|
||||
[236,177,85],
|
||||
[121,147,160],
|
||||
[234,218,110],
|
||||
[241,157,191],
|
||||
[62,200,234],
|
||||
[133,243,34],
|
||||
[88,149,110],
|
||||
[226,235,237],
|
||||
[183,119,118],
|
||||
[192,247,192],
|
||||
[113,196,122],
|
||||
[197,115,70],
|
||||
[80,175,187],
|
||||
[103,231,238],
|
||||
[240,72,133],
|
||||
[228,149,241],
|
||||
[180,188,159],
|
||||
[172,132,85],
|
||||
[221,236,102],
|
||||
[236,194,58],
|
||||
[217,176,109],
|
||||
[88,244,199],
|
||||
[186,157,239],
|
||||
[113,230,96],
|
||||
[206,115,165],
|
||||
[244,178,163],
|
||||
[230,139,26],
|
||||
[241,125,89],
|
||||
[83,160,66],
|
||||
[107,190,166],
|
||||
[197,161,210],
|
||||
[198,203,245],
|
||||
[238,117,19],
|
||||
[228,119,116],
|
||||
[131,156,41],
|
||||
[145,178,168],
|
||||
[139,170,220],
|
||||
[233,95,125],
|
||||
[87,178,230],
|
||||
[157,200,119],
|
||||
[237,140,76],
|
||||
[229,185,186],
|
||||
[144,206,212],
|
||||
[236,209,158],
|
||||
[185,189,79],
|
||||
[34,208,66],
|
||||
[84,238,129],
|
||||
[133,140,134],
|
||||
[219,229,175],
|
||||
[168,179,25],
|
||||
[140,145,240],
|
||||
[151,241,125],
|
||||
[67,162,107],
|
||||
[200,156,21],
|
||||
[169,173,189],
|
||||
[226,116,189],
|
||||
[133,231,191],
|
||||
[194,161,63],
|
||||
[241,77,99],
|
||||
[241,217,53],
|
||||
[123,204,105],
|
||||
[210,201,119],
|
||||
[229,108,155],
|
||||
[240,91,72],
|
||||
[187,115,210],
|
||||
[240,163,100],
|
||||
[178,217,57],
|
||||
[179,135,116],
|
||||
[204,211,24],
|
||||
[186,135,57],
|
||||
[223,176,135],
|
||||
[204,148,151],
|
||||
[116,223,50],
|
||||
[95,195,46],
|
||||
[123,160,236],
|
||||
[181,172,131],
|
||||
[142,220,202],
|
||||
[240,140,112],
|
||||
[172,145,164],
|
||||
[228,124,45],
|
||||
[222,234,142],
|
||||
[42,205,125],
|
||||
[192,233,116],
|
||||
[119,170,114],
|
||||
[158,138,26],
|
||||
[73,190,183],
|
||||
[185,229,243],
|
||||
[227,107,55],
|
||||
[196,205,202],
|
||||
[132,143,60],
|
||||
[233,192,237],
|
||||
[62,150,220],
|
||||
[205,201,141],
|
||||
[106,140,190],
|
||||
[161,131,205],
|
||||
[226,226,75],
|
||||
[198,139,81],
|
||||
[115,171,32],
|
||||
[101,181,67],
|
||||
[149,137,119],
|
||||
[222,245,86],
|
||||
[183,130,175],
|
||||
[168,125,133],
|
||||
[124,142,87],
|
||||
[236,156,171],
|
||||
[232,194,91],
|
||||
[219,200,69],
|
||||
[144,219,34],
|
||||
[219,95,187],
|
||||
[145,154,217],
|
||||
[165,185,100],
|
||||
[127,238,163],
|
||||
[224,178,198],
|
||||
[119,153,120],
|
||||
[124,212,92],
|
||||
[172,161,105],
|
||||
[231,155,135],
|
||||
[157,132,101],
|
||||
[122,185,146],
|
||||
[53,166,51],
|
||||
[70,163,90],
|
||||
[150,190,213],
|
||||
[234,235,219],
|
||||
[166,152,185],
|
||||
[159,194,159],
|
||||
[222,242,47],
|
||||
[202,176,161],
|
||||
[95,140,229],
|
||||
[156,246,80],
|
||||
[93,170,203],
|
||||
[159,142,54],
|
||||
[185,237,230],
|
||||
[94,150,149],
|
||||
[187,206,136],
|
||||
[157,224,166],
|
||||
[235,158,208],
|
||||
[109,232,216],
|
||||
[141,201,87],
|
||||
[208,124,118],
|
||||
[142,125,214],
|
||||
[226,234,123],
|
||||
[72,219,41],
|
||||
[234,102,111],
|
||||
[168,142,79],
|
||||
[188,135,35],
|
||||
[214,233,195],
|
||||
[148,173,116],
|
||||
[223,112,95],
|
||||
[228,128,236],
|
||||
[206,114,54],
|
||||
[195,119,88],
|
||||
[235,140,94],
|
||||
[235,202,125],
|
||||
[233,155,153],
|
||||
[214,214,238],
|
||||
[246,200,35],
|
||||
[151,125,171],
|
||||
[132,145,172],
|
||||
[131,142,118],
|
||||
[199,126,150],
|
||||
[61,162,123],
|
||||
[58,176,151],
|
||||
[215,141,69],
|
||||
[225,154,220],
|
||||
[224,244,174],
|
||||
[233,161,64],
|
||||
[130,221,137],
|
||||
[81,191,129],
|
||||
[169,162,140],
|
||||
[174,177,222],
|
||||
[236,174,47],
|
||||
[196,240,159],
|
||||
[69,222,172],
|
||||
[71,232,93],
|
||||
[118,211,238],
|
||||
[157,224,83],
|
||||
[218,105,73],
|
||||
[165,241,221]]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
desc "re-generate avatars"
|
||||
task "avatars:regenerate" => :environment do
|
||||
RailsMultisite::ConnectionManagement.each_connection do |db|
|
||||
puts "Generating avatars for: #{db}"
|
||||
next unless SiteSetting.allow_uploaded_avatars
|
||||
User.where("uploaded_avatar_id IS NOT NULL").find_each do |u|
|
||||
Jobs.enqueue(:generate_avatars, upload_id: u.uploaded_avatar_id, user_id: u.id)
|
||||
putc "."
|
||||
end
|
||||
end
|
||||
puts "\ndone."
|
||||
end
|
|
@ -1,67 +0,0 @@
|
|||
# encoding: utf-8
|
||||
require 'spec_helper'
|
||||
require_dependency 'avatar_detector'
|
||||
|
||||
describe AvatarDetector do
|
||||
|
||||
describe "construction" do
|
||||
|
||||
it "raises an error without a user" do
|
||||
-> { AvatarDetector.new(nil) }.should raise_error
|
||||
end
|
||||
|
||||
it "raises an error on a non-user object" do
|
||||
-> { AvatarDetector.new(Array.new) }.should raise_error
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "has_custom_avatar?" do
|
||||
|
||||
describe "with a user" do
|
||||
let(:user) { User.new(use_uploaded_avatar: true) }
|
||||
let(:avatar_detector) { AvatarDetector.new(user) }
|
||||
|
||||
describe "when the user doesn't have an uploaded_avatar_path" do
|
||||
|
||||
before do
|
||||
user.stubs(:uploaded_avatar_path)
|
||||
end
|
||||
|
||||
it "returns true if they have a custom gravatar" do
|
||||
avatar_detector.expects(:has_custom_gravatar?).returns(true)
|
||||
avatar_detector.has_custom_avatar?.should be_true
|
||||
end
|
||||
|
||||
it "returns false if they don't have a custom gravatar" do
|
||||
avatar_detector.expects(:has_custom_gravatar?).returns(false)
|
||||
avatar_detector.has_custom_avatar?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context "when the user doesn't have an uploaded_avatar_path" do
|
||||
let(:user) { User.new(use_uploaded_avatar: true) }
|
||||
let(:avatar_detector) { AvatarDetector.new(user) }
|
||||
|
||||
describe "when the user has an uploaded avatar" do
|
||||
before do
|
||||
user.expects(:uploaded_avatar_path).returns("/some/uploaded/file.png")
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
avatar_detector.has_custom_avatar?.should be_true
|
||||
end
|
||||
|
||||
it "doesn't call has_custom_gravatar" do
|
||||
avatar_detector.expects(:has_custom_gravatar?).never
|
||||
avatar_detector.has_custom_avatar?
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -112,7 +112,7 @@ describe ComposerMessagesFinder do
|
|||
end
|
||||
|
||||
it "doesn't return notifications for users who have custom avatars" do
|
||||
user.user_stat.has_custom_avatar = true
|
||||
user.uploaded_avatar_id = 1
|
||||
finder.check_avatar_notification.should be_blank
|
||||
end
|
||||
|
||||
|
|
|
@ -1136,7 +1136,6 @@ describe UsersController do
|
|||
upload = Fabricate(:upload)
|
||||
Upload.expects(:create_for).returns(upload)
|
||||
# enqueues the user_image generator job
|
||||
Jobs.expects(:enqueue).with(:generate_avatars, { user_id: user.id, upload_id: upload.id })
|
||||
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "avatar"
|
||||
user.reload
|
||||
# erase the previous template
|
||||
|
@ -1194,7 +1193,6 @@ describe UsersController do
|
|||
upload = Fabricate(:upload)
|
||||
Upload.expects(:create_for).returns(upload)
|
||||
# enqueues the user_image generator job
|
||||
Jobs.expects(:enqueue).with(:generate_avatars, { user_id: user.id, upload_id: upload.id })
|
||||
xhr :post, :upload_avatar, username: user.username, file: user_image_url, user_image_type: "avatar"
|
||||
user.reload
|
||||
# erase the previous template
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe UserAvatar do
|
||||
let(:avatar){
|
||||
user = Fabricate(:user)
|
||||
user.create_user_avatar!
|
||||
}
|
||||
|
||||
it 'can generate a system avatar' do
|
||||
avatar.update_system_avatar!
|
||||
avatar.system_upload.should_not be_nil
|
||||
end
|
||||
|
||||
it 'can update gravatars' do
|
||||
temp = Tempfile.new('test')
|
||||
FileHelper.expects(:download).returns(temp)
|
||||
avatar.update_gravatar!
|
||||
temp.unlink
|
||||
avatar.gravatar_upload.should_not be_nil
|
||||
end
|
||||
end
|
|
@ -984,47 +984,30 @@ describe User do
|
|||
|
||||
describe ".small_avatar_url" do
|
||||
|
||||
let(:user) { build(:user, use_uploaded_avatar: true, uploaded_avatar_template: "/uploaded/avatar/template/{size}.png") }
|
||||
let(:user) { build(:user, username: 'Sam') }
|
||||
|
||||
it "returns a 45-pixel-wide avatar" do
|
||||
user.small_avatar_url.should == "//test.localhost/uploaded/avatar/template/45.png"
|
||||
user.small_avatar_url.should == "//test.localhost/avatar/sam/45/-1.png"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe ".uploaded_avatar_path" do
|
||||
describe ".avatar_template_url" do
|
||||
|
||||
let(:user) { build(:user, use_uploaded_avatar: true, uploaded_avatar_template: "/uploaded/avatar/template/{size}.png") }
|
||||
let(:user) { build(:user, uploaded_avatar_id: 99, username: 'Sam') }
|
||||
|
||||
it "returns nothing when uploaded avatars are not allowed" do
|
||||
SiteSetting.expects(:allow_uploaded_avatars).returns(false)
|
||||
user.uploaded_avatar_path.should be_nil
|
||||
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" do
|
||||
user.uploaded_avatar_path.should == "//test.localhost/uploaded/avatar/template/{size}.png"
|
||||
it "returns a schemaless avatar template with correct id" do
|
||||
user.avatar_template_url.should == "//test.localhost/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.uploaded_avatar_path.should == "//my.cdn.com/uploaded/avatar/template/{size}.png"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe ".avatar_template" do
|
||||
|
||||
let(:user) { build(:user, email: "em@il.com") }
|
||||
|
||||
it "returns the uploaded_avatar_path by default" do
|
||||
user.expects(:uploaded_avatar_path).returns("//discourse.org/uploaded/avatar.png")
|
||||
user.avatar_template.should == "//discourse.org/uploaded/avatar.png"
|
||||
end
|
||||
|
||||
it "returns the gravatar when no avatar has been uploaded" do
|
||||
user.expects(:uploaded_avatar_path)
|
||||
User.expects(:gravatar_template).with(user.email).returns("//gravatar.com/avatar.png")
|
||||
user.avatar_template.should == "//gravatar.com/avatar.png"
|
||||
user.avatar_template_url.should == "//my.cdn.com/avatar/sam/{size}/99.png"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -33,6 +33,9 @@ Spork.prefork do
|
|||
|
||||
# let's not run seed_fu every test
|
||||
SeedFu.quiet = true if SeedFu.respond_to? :quiet
|
||||
|
||||
SiteSetting.enable_system_avatars = false
|
||||
SiteSetting.automatically_download_gravatars = false
|
||||
SeedFu.seed
|
||||
|
||||
RSpec.configure do |config|
|
||||
|
@ -79,6 +82,10 @@ Spork.prefork do
|
|||
SiteSetting.remove_override!(setting.name)
|
||||
end
|
||||
|
||||
# very expensive IO operations
|
||||
SiteSetting.enable_system_avatars = false
|
||||
SiteSetting.automatically_download_gravatars = false
|
||||
|
||||
end
|
||||
|
||||
class TestCurrentUserProvider < Auth::DefaultCurrentUserProvider
|
||||
|
|
Loading…
Reference in New Issue