Feature #12197 » 0001-Allow-users-to-set-their-avatar-in-Redmine.patch
| app/assets/stylesheets/application.css | ||
|---|---|---|
| 2364 | 2364 |
} |
| 2365 | 2365 |
h2 .avatar, h3 .avatar {margin-inline-end: 4px;}
|
| 2366 |
h4 img.gravatar {margin-block: -2px -4px; margin-inline: 0 4px;}
|
|
| 2366 |
h4 img.avatar {margin-block: -2px -4px; margin-inline: 0 4px;}
|
|
| 2367 | 2367 |
#activity dt .avatar {
|
| 2368 | 2368 |
margin-block: 0; |
| 2369 | 2369 |
margin-inline: 0 1em; |
| app/assets/stylesheets/responsive.css | ||
|---|---|---|
| 494 | 494 |
border-block-start: 1px solid rgba(255,255,255,.1); |
| 495 | 495 |
} |
| 496 |
.flyout-menu__avatar img.gravatar {
|
|
| 496 |
.flyout-menu__avatar img.avatar {
|
|
| 497 | 497 |
inline-size: 40px; |
| 498 | 498 |
block-size: 40px; |
| 499 | 499 |
padding: 0; |
| app/controllers/avatars_controller.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006- Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
class AvatarsController < ApplicationController |
|
| 21 |
before_action :find_avatar |
|
| 22 |
before_action :check_avatar_available |
|
| 23 | ||
| 24 |
def show |
|
| 25 |
# The original image is never served since it may contain metadata |
|
| 26 |
# such as the location where it was taken |
|
| 27 |
file = @avatar.thumbnail(:size => params[:size], :crop => true) |
|
| 28 |
unless file |
|
| 29 |
render_404 |
|
| 30 |
return |
|
| 31 |
end |
|
| 32 | ||
| 33 |
expires_in 1.day, :public => !Setting.login_required? |
|
| 34 |
if stale?(:etag => file, :template => false) |
|
| 35 |
send_file( |
|
| 36 |
file, |
|
| 37 |
:filename => filename_for_content_disposition(@avatar.filename), |
|
| 38 |
:type => @avatar.content_type, |
|
| 39 |
:disposition => 'inline') |
|
| 40 |
end |
|
| 41 |
end |
|
| 42 | ||
| 43 |
private |
|
| 44 | ||
| 45 |
def find_avatar |
|
| 46 |
user = User.find_by(:id => params[:user_id], :avatar_id => params[:id]) |
|
| 47 |
@avatar = user&.avatar |
|
| 48 |
render_404 unless @avatar&.readable? |
|
| 49 |
end |
|
| 50 | ||
| 51 |
def check_avatar_available |
|
| 52 |
render_404 unless User.avatar_available? |
|
| 53 |
end |
|
| 54 |
end |
|
| app/helpers/avatars_helper.rb | ||
|---|---|---|
| 34 | 34 |
avatar(user, options).to_s.html_safe |
| 35 | 35 |
end |
| 36 |
# Returns the full URL of the avatar image of +user+ for the API, or nil |
|
| 37 |
def avatar_image_url(user) |
|
| 38 |
if local_avatar?(user) |
|
| 39 |
user_avatar_url(user, user.avatar_id) |
|
| 40 |
elsif user.mail && Setting.gravatar_enabled? |
|
| 41 |
gravatar_url(user.mail, {rating: nil, size: nil, default: Setting.gravatar_default})
|
|
| 42 |
end |
|
| 43 |
end |
|
| 44 | ||
| 36 | 45 |
# Returns the avatar image tag for the given +user+ if avatars are enabled |
| 37 | 46 |
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') |
| 38 | 47 |
def avatar(user, options = {})
|
| ... | ... | |
| 46 | 55 |
anonymous_avatar(options) |
| 47 | 56 |
elsif user.is_a?(Group) |
| 48 | 57 |
group_avatar(options) |
| 58 |
elsif local_avatar?(user) |
|
| 59 |
local_avatar_tag(user, options) |
|
| 49 | 60 |
elsif Setting.gravatar_enabled? |
| 50 | 61 |
gravatar_avatar_tag(user, options) |
| 51 | 62 |
elsif user.respond_to?(:initials) |
| ... | ... | |
| 75 | 86 |
private |
| 87 |
def local_avatar?(user) |
|
| 88 |
user.is_a?(User) && user.avatar_id && User.avatar_available? |
|
| 89 |
end |
|
| 90 | ||
| 91 |
def local_avatar_tag(user, options) |
|
| 92 |
size = (options.delete(:size) || GravatarHelper::DEFAULT_OPTIONS[:size]).to_i |
|
| 93 |
options[:title] ||= user.name |
|
| 94 |
options[:srcset] = "#{user_avatar_path(user, user.avatar_id, :size => size * 2)} 2x"
|
|
| 95 |
image_tag( |
|
| 96 |
user_avatar_path(user, user.avatar_id, :size => size), |
|
| 97 |
GravatarHelper::DEFAULT_OPTIONS.slice(:alt).merge(options).merge(:width => size, :height => size)) |
|
| 98 |
end |
|
| 99 | ||
| 76 | 100 |
def gravatar_avatar_tag(user, options) |
| 77 | 101 |
options[:default] = Setting.gravatar_default |
| 78 | 102 |
options[:class] = [GravatarHelper::DEFAULT_OPTIONS[:class], options[:class]].compact.join(' ')
|
| app/models/attachment.rb | ||
|---|---|---|
| 262 | 262 |
# Returns the full path the attachment thumbnail, or nil |
| 263 | 263 |
# if the thumbnail cannot be generated. |
| 264 |
# Pass :crop => true to get a square thumbnail cropped around the center. |
|
| 264 | 265 |
def thumbnail(options={})
|
| 265 | 266 |
if thumbnailable? && readable? |
| 266 | 267 |
size = options[:size].to_i |
| ... | ... | |
| 274 | 275 |
size = Setting.thumbnails_size.to_i |
| 275 | 276 |
end |
| 276 | 277 |
size = 100 unless size > 0 |
| 277 |
target = thumbnail_path(size) |
|
| 278 |
crop = !!options[:crop] |
|
| 279 |
target = thumbnail_path(crop ? "#{size}_crop" : size)
|
|
| 278 | 280 |
begin |
| 279 | 281 |
# TODO: Stop passing the deprecated is_pdf flag in Redmine 7.0 |
| 280 |
Redmine::Thumbnail.generate(self.diskfile, target, size, is_pdf?) |
|
| 282 |
Redmine::Thumbnail.generate(self.diskfile, target, size, is_pdf?, :crop => crop)
|
|
| 281 | 283 |
rescue => e |
| 282 | 284 |
if logger |
| 283 | 285 |
logger.error( |
| app/models/user.rb | ||
|---|---|---|
| 109 | 109 |
:foreign_key => :resource_owner_id, :dependent => :delete_all |
| 110 | 110 |
belongs_to :auth_source |
| 111 |
belongs_to :avatar, :class_name => 'Attachment', :dependent => :destroy |
|
| 111 | 112 |
scope :logged, lambda {where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}")}
|
| 112 | 113 |
scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i})}
|
| ... | ... | |
| 120 | 121 |
attr_accessor :remote_ip |
| 121 | 122 |
attr_writer :oauth_scope |
| 123 |
AVATAR_CONTENT_TYPES = %w(image/avif image/gif image/jpeg image/png image/webp).freeze |
|
| 124 | ||
| 122 | 125 |
LOGIN_LENGTH_LIMIT = 60 |
| 123 | 126 |
MAIL_LENGTH_LIMIT = 254 |
| ... | ... | |
| 425 | 428 |
self.preference ||= UserPreference.new(:user => self) |
| 426 | 429 |
end |
| 430 |
# Avatars are served only as thumbnails, which require ImageMagick |
|
| 431 |
def self.avatar_available? |
|
| 432 |
Setting.avatar_enabled? && Redmine::Thumbnail.convert_available? |
|
| 433 |
end |
|
| 434 | ||
| 435 |
# Replaces the avatar with the uploaded +file+ and returns true, or returns |
|
| 436 |
# false with errors if the file cannot be used as an avatar |
|
| 437 |
def update_avatar(file) |
|
| 438 |
attachment = Attachment.new(:file => file, :author => User.current, :container => self) |
|
| 439 |
# Avatars are served only as thumbnails |
|
| 440 |
unless User.avatar_available? && AVATAR_CONTENT_TYPES.include?(Marcel::MimeType.for(file)) && attachment.thumbnailable? |
|
| 441 |
errors.add(:avatar, :invalid) |
|
| 442 |
return false |
|
| 443 |
end |
|
| 444 | ||
| 445 |
unless attachment.valid? |
|
| 446 |
attachment.errors.full_messages.each {|message| errors.add(:base, message)}
|
|
| 447 |
return false |
|
| 448 |
end |
|
| 449 | ||
| 450 |
transaction do |
|
| 451 |
attachment.save! |
|
| 452 |
replace_avatar(attachment) |
|
| 453 |
end |
|
| 454 |
true |
|
| 455 |
end |
|
| 456 | ||
| 457 |
def delete_avatar |
|
| 458 |
replace_avatar(nil) |
|
| 459 |
end |
|
| 460 | ||
| 461 |
# Unlike the thumbnails, the original image of the avatar may contain |
|
| 462 |
# metadata such as the location where it was taken |
|
| 463 |
def attachments_visible?(user=User.current) |
|
| 464 |
user == self || user.admin? |
|
| 465 |
end |
|
| 466 | ||
| 467 |
# Avatars can only be changed through AvatarsController |
|
| 468 |
def attachments_editable?(user=User.current) |
|
| 469 |
false |
|
| 470 |
end |
|
| 471 | ||
| 472 |
def attachments_deletable?(user=User.current) |
|
| 473 |
false |
|
| 474 |
end |
|
| 475 | ||
| 427 | 476 |
def time_zone |
| 428 | 477 |
@time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone]) |
| 429 | 478 |
end |
| ... | ... | |
| 975 | 1024 |
private |
| 1025 |
def replace_avatar(attachment) |
|
| 1026 |
old_avatar = avatar |
|
| 1027 |
self.avatar = attachment |
|
| 1028 |
update_column(:avatar_id, avatar_id) |
|
| 1029 |
old_avatar&.destroy |
|
| 1030 |
end |
|
| 1031 | ||
| 976 | 1032 |
def generate_password_if_needed |
| 977 | 1033 |
if generate_password? && auth_source.nil? |
| 978 | 1034 |
length = [Setting.password_min_length.to_i + 2, 10].max |
| app/views/settings/_display.html.erb | ||
|---|---|---|
| 19 | 19 |
<p><%= setting_select :user_format, @options[:user_format] %></p> |
| 20 |
<p><%= setting_check_box :avatar_enabled %> |
|
| 21 |
<% unless Redmine::Thumbnail.convert_available? %> |
|
| 22 |
<em class="info"><span class="icon icon-warning"><%= sprite_icon('warning', l(:text_setting_avatar_enabled_imagemagick_unavailable)) %></span></em>
|
|
| 23 |
<% end %></p> |
|
| 24 | ||
| 20 | 25 |
<p><%= setting_check_box :gravatar_enabled, :data => {:enables => '#settings_gravatar_default'} %>
|
| 21 | 26 |
<em class="info"><%= t(:text_avatar_server_config_html, :url => Redmine::Configuration['avatar_server_url']) %></em></p> |
| app/views/users/index.api.rsb | ||
|---|---|---|
| 11 | 11 |
api.updated_on user.updated_on |
| 12 | 12 |
api.last_login_on user.last_login_on |
| 13 | 13 |
api.passwd_changed_on user.passwd_changed_on |
| 14 |
api.avatar_url gravatar_url(user.mail, {rating: nil, size: nil, default: Setting.gravatar_default}) if Setting.gravatar_enabled?
|
|
| 14 |
avatar_url = avatar_image_url(user) |
|
| 15 |
api.avatar_url avatar_url if avatar_url |
|
| 15 | 16 |
api.twofa_scheme user.twofa_scheme |
| 16 | 17 |
api.status user.status |
| app/views/users/show.api.rsb | ||
|---|---|---|
| 9 | 9 |
api.updated_on @user.updated_on |
| 10 | 10 |
api.last_login_on @user.last_login_on |
| 11 | 11 |
api.passwd_changed_on @user.passwd_changed_on |
| 12 |
api.avatar_url gravatar_url(@user.mail, {rating: nil, size: nil, default: Setting.gravatar_default}) if @user.mail && Setting.gravatar_enabled?
|
|
| 12 |
avatar_url = avatar_image_url(@user) |
|
| 13 |
api.avatar_url avatar_url if avatar_url |
|
| 13 | 14 |
api.twofa_scheme @user.twofa_scheme if User.current.admin? || (User.current == @user) |
| 14 | 15 |
api.api_key @user.api_key if (User.current.admin? || (User.current == @user && !User.current.authorized_by_oauth?)) |
| 15 | 16 |
api.status @user.status if User.current.admin? |
| config/locales/en.yml | ||
|---|---|---|
| 357 | 357 |
field_start_date: Start date |
| 358 | 358 |
field_done_ratio: "% Done" |
| 359 | 359 |
field_auth_source: Authentication mode |
| 360 |
field_avatar: Avatar |
|
| 360 | 361 |
field_hide_mail: Hide my email address |
| 361 | 362 |
field_comments: Comment |
| 362 | 363 |
field_url: URL |
| ... | ... | |
| 479 | 480 |
setting_mail_handler_preferred_body_part: Preferred part of multipart (HTML) emails |
| 480 | 481 |
setting_sys_api_key: Repository management WS API key |
| 481 | 482 |
setting_sequential_project_identifiers: Generate sequential project identifiers |
| 483 |
setting_avatar_enabled: Allow users to set their avatar |
|
| 482 | 484 |
setting_gravatar_enabled: Use Gravatar user icons |
| 483 | 485 |
setting_gravatar_default: Default Gravatar image |
| 484 | 486 |
setting_diff_max_lines_displayed: Maximum number of diff lines displayed |
| ... | ... | |
| 1398 | 1400 |
text_allowed_queries_to_select: Public (to any users) queries only selectable |
| 1399 | 1401 |
text_setting_config_change: You can configure the behaviour in config/configuration.yml. Please restart the application after editing it. |
| 1400 | 1402 |
text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="%{url}">%{url}</a> to generate their avatars.
|
| 1403 |
text_setting_avatar_enabled_imagemagick_unavailable: ImageMagick is not available, so users cannot set their avatar. |
|
| 1401 | 1404 |
text_oauth_admin_permission: Full administrative access. When authorized by an Administrator, this application will be able to read and write all data and impersonate other users. |
| 1402 | 1405 |
text_oauth_admin_permission_info: This application requests full administrative access. If you are an Administrator (or become one in the future), it will be able to read and write all data and impersonate other users on your behalf. If you want to avoid this, authorize it as a user without Administrator privileges instead. |
| 1403 | 1406 |
text_oauth_copy_secret_now: Copy the secret to a safe place now, it will not be shown again. |
| config/routes.rb | ||
|---|---|---|
| 129 | 129 |
resources :memberships, :controller => 'principal_memberships' |
| 130 | 130 |
resources :email_addresses, :only => [:index, :create, :update, :destroy] |
| 131 | 131 |
end |
| 132 |
get 'users/:user_id/avatar/:id(/:size)', :to => 'avatars#show', :user_id => /\d+/, :id => /\d+/, :size => /\d+/, :as => 'user_avatar' |
|
| 132 | 133 |
post 'watchers/watch', :to => 'watchers#watch', :as => 'watch' |
| 133 | 134 |
delete 'watchers/watch', :to => 'watchers#unwatch' |
| config/settings.yml | ||
|---|---|---|
| 328 | 328 |
default: |- |
| 329 | 329 |
You have received this notification because you have either subscribed to it, or are involved in it. |
| 330 | 330 |
To change your notification preferences, please click here: http://hostname/my/account |
| 331 |
avatar_enabled: |
|
| 332 |
default: 1 |
|
| 331 | 333 |
gravatar_enabled: |
| 332 | 334 |
default: 0 |
| 333 | 335 |
gravatar_default: |
| db/migrate/20261015000046_add_avatar_id_to_users.rb | ||
|---|---|---|
| 1 |
class AddAvatarIdToUsers < ActiveRecord::Migration[8.1] |
|
| 2 |
def change |
|
| 3 |
add_column :users, :avatar_id, :integer |
|
| 4 |
end |
|
| 5 |
end |
|
| lib/redmine/thumbnail.rb | ||
|---|---|---|
| 33 | 33 |
ALLOWED_TYPES = %w(image/avif image/bmp image/gif image/jpeg image/png image/webp application/pdf) |
| 34 | 34 |
# Generates a thumbnail for the source image to target |
| 35 |
# If crop is true, the image is cropped to a square around its center |
|
| 35 | 36 |
# TODO: Remove the deprecated _is_pdf parameter in Redmine 7.0 |
| 36 |
def self.generate(source, target, size, _is_pdf = nil) |
|
| 37 |
def self.generate(source, target, size, _is_pdf = nil, crop: false)
|
|
| 37 | 38 |
return nil unless convert_available? |
| 38 | 39 |
unless File.exist?(target) |
| ... | ... | |
| 54 | 55 |
return nil unless valid_pdf_magic?(source) |
| 55 | 56 |
cmd = "#{shell_quote CONVERT_BIN} #{shell_quote "#{source}[0]"} -thumbnail #{shell_quote size_option} #{shell_quote "png:#{output}"}"
|
| 57 |
elsif crop |
|
| 58 |
cmd = "#{shell_quote CONVERT_BIN} #{shell_quote source} -auto-orient -thumbnail #{shell_quote "#{size}x#{size}^"} " \
|
|
| 59 |
"-gravity center -extent #{shell_quote "#{size}x#{size}"} #{shell_quote output}"
|
|
| 56 | 60 |
else |
| 57 | 61 |
cmd = "#{shell_quote CONVERT_BIN} #{shell_quote source} -auto-orient -thumbnail #{shell_quote size_option} #{shell_quote output}"
|
| 58 | 62 |
end |
| test/functional/avatars_controller_test.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006- Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
require_relative '../test_helper' |
|
| 21 | ||
| 22 |
class AvatarsControllerTest < Redmine::ControllerTest |
|
| 23 |
def setup |
|
| 24 |
User.current = nil |
|
| 25 |
set_fixtures_attachments_directory |
|
| 26 |
User.find(2).update_column(:avatar_id, 16) |
|
| 27 |
end |
|
| 28 | ||
| 29 |
def teardown |
|
| 30 |
set_tmp_attachments_directory |
|
| 31 |
end |
|
| 32 | ||
| 33 |
def test_show |
|
| 34 |
skip unless convert_installed? |
|
| 35 | ||
| 36 |
get :show, :params => {:user_id => 2, :id => 16, :size => 48}
|
|
| 37 |
assert_response :success |
|
| 38 |
assert_equal 'image/png', response.media_type |
|
| 39 |
assert_match /\Ainline/, response.headers['Content-Disposition'] |
|
| 40 |
assert_equal "max-age=#{1.day.to_i}, public", response.headers['Cache-Control']
|
|
| 41 |
end |
|
| 42 | ||
| 43 |
def test_show_should_not_serve_original_image_if_thumbnail_cannot_be_generated |
|
| 44 |
Attachment.any_instance.stubs(:thumbnail).returns(nil) |
|
| 45 |
get :show, :params => {:user_id => 2, :id => 16}
|
|
| 46 |
assert_response :not_found |
|
| 47 |
end |
|
| 48 | ||
| 49 |
def test_show_should_return_404_with_avatars_disabled |
|
| 50 |
Redmine::Thumbnail.stubs(:convert_available?).returns(true) |
|
| 51 |
with_settings :avatar_enabled => '0' do |
|
| 52 |
get :show, :params => {:user_id => 2, :id => 16}
|
|
| 53 |
end |
|
| 54 |
assert_response :not_found |
|
| 55 |
end |
|
| 56 | ||
| 57 |
def test_show_should_return_404_for_replaced_avatar |
|
| 58 |
get :show, :params => {:user_id => 2, :id => 15}
|
|
| 59 |
assert_response :not_found |
|
| 60 |
end |
|
| 61 | ||
| 62 |
def test_show_should_require_login_if_login_is_required |
|
| 63 |
with_settings :login_required => '1' do |
|
| 64 |
get :show, :params => {:user_id => 2, :id => 16}
|
|
| 65 |
assert_response :redirect |
|
| 66 |
end |
|
| 67 |
end |
|
| 68 |
end |
|
| test/helpers/avatars_helper_test.rb | ||
|---|---|---|
| 31 | 31 |
assert_include Digest::SHA256.hexdigest('jsmith@somenet.foo'), avatar(User.find_by_mail('jsmith@somenet.foo'))
|
| 32 | 32 |
end |
| 33 |
def test_avatar_with_user_having_local_avatar |
|
| 34 |
Redmine::Thumbnail.stubs(:convert_available?).returns(true) |
|
| 35 |
user = User.find(2) |
|
| 36 |
user.avatar_id = 16 |
|
| 37 | ||
| 38 |
avatar_tag = avatar(user, :size => 24) |
|
| 39 |
assert_include 'src="/users/2/avatar/16/24"', avatar_tag |
|
| 40 |
assert_include 'srcset="/users/2/avatar/16/48 2x"', avatar_tag |
|
| 41 |
assert_include 'width="24" height="24"', avatar_tag |
|
| 42 |
end |
|
| 43 | ||
| 44 |
def test_avatar_should_ignore_local_avatar_without_imagemagick |
|
| 45 |
Redmine::Thumbnail.stubs(:convert_available?).returns(false) |
|
| 46 |
user = User.find(2) |
|
| 47 |
user.avatar_id = 16 |
|
| 48 | ||
| 49 |
assert_include Digest::SHA256.hexdigest('jsmith@somenet.foo'), avatar(user)
|
|
| 50 |
end |
|
| 51 | ||
| 33 | 52 |
def test_avatar_with_email_string |
| 34 | 53 |
assert_include Digest::SHA256.hexdigest('jsmith@somenet.foo'), avatar('jsmith <jsmith@somenet.foo>')
|
| 35 | 54 |
end |
| test/integration/api_test/users_test.rb | ||
|---|---|---|
| 168 | 168 |
assert_select 'user avatar_url', :text => %r|\Ahttps://gravatar.com/avatar/\h{64}\?default=robohash|
|
| 169 | 169 |
end |
| 170 |
test "GET /users/:id.xml should return the URL of the uploaded avatar" do |
|
| 171 |
Redmine::Thumbnail.stubs(:convert_available?).returns(true) |
|
| 172 |
User.find(2).update_column(:avatar_id, 16) |
|
| 173 |
with_settings :gravatar_enabled => '1' do |
|
| 174 |
get '/users/2.xml' |
|
| 175 |
end |
|
| 176 | ||
| 177 |
assert_response :success |
|
| 178 |
assert_select 'user avatar_url', :text => 'http://www.example.com/users/2/avatar/16' |
|
| 179 |
end |
|
| 180 | ||
| 170 | 181 |
test "GET /users/:id.xml should not return avatar_url when not set email address" do |
| 171 | 182 |
user = User.find(2) |
| 172 | 183 |
user.email_addresses.delete_all |
| test/integration/routing/users_test.rb | ||
|---|---|---|
| 34 | 34 |
should_route 'DELETE /users/bulk_destroy' => 'users#bulk_destroy' |
| 35 | 35 |
should_route 'POST /users/bulk_lock' => 'users#bulk_lock' |
| 36 | 36 |
should_route 'POST /users/bulk_unlock' => 'users#bulk_unlock' |
| 37 | ||
| 38 |
should_route 'GET /users/44/avatar/2/48' => 'avatars#show', :user_id => '44', :id => '2', :size => '48' |
|
| 37 | 39 |
end |
| 38 | 40 |
end |
| test/unit/lib/redmine/thumbnail_test.rb | ||
|---|---|---|
| 68 | 68 |
ensure |
| 69 | 69 |
FileUtils.rm_f(target) if target |
| 70 | 70 |
end |
| 71 | ||
| 72 |
def test_thumbnail_with_crop_should_be_square |
|
| 73 |
skip unless Redmine::Thumbnail.convert_available? |
|
| 74 | ||
| 75 |
set_tmp_attachments_directory |
|
| 76 |
file = file_fixture('2010/11/101123161450_testfile_1.png')
|
|
| 77 |
target = File.join(Attachment.storage_path, "#{SecureRandom.hex(8)}.thumb")
|
|
| 78 |
Redmine::Thumbnail.generate(file.to_s, target, 50, :crop => true) |
|
| 79 |
# Width and height in the IHDR chunk of the PNG file |
|
| 80 |
assert_equal [50, 50], File.binread(target, 8, 16).unpack('NN')
|
|
| 81 |
ensure |
|
| 82 |
FileUtils.rm_f(target) if target |
|
| 83 |
end |
|
| 71 | 84 |
end |
| test/unit/user_test.rb | ||
|---|---|---|
| 1568 | 1568 |
users(:users_004).destroy |
| 1569 | 1569 |
end |
| 1570 | 1570 |
end |
| 1571 | ||
| 1572 |
def test_update_avatar_should_save_image_as_attachment_of_user |
|
| 1573 |
set_tmp_attachments_directory |
|
| 1574 |
assert update_avatar(@jsmith, '2010/11/101123161450_testfile_1.png') |
|
| 1575 | ||
| 1576 |
avatar = @jsmith.reload.avatar |
|
| 1577 |
assert_equal @jsmith, avatar.container |
|
| 1578 |
assert_equal 'image/png', avatar.content_type |
|
| 1579 |
end |
|
| 1580 | ||
| 1581 |
def test_avatar_attachment_should_be_visible_only_to_user_and_administrators |
|
| 1582 |
set_tmp_attachments_directory |
|
| 1583 |
update_avatar(@jsmith, '2010/11/101123161450_testfile_1.png') |
|
| 1584 | ||
| 1585 |
avatar = @jsmith.reload.avatar |
|
| 1586 |
assert avatar.visible?(@jsmith) |
|
| 1587 |
assert avatar.visible?(@admin) |
|
| 1588 |
assert_not avatar.visible?(@dlopper) |
|
| 1589 |
end |
|
| 1590 | ||
| 1591 |
def test_avatar_available_should_require_setting_and_imagemagick |
|
| 1592 |
Redmine::Thumbnail.stubs(:convert_available?).returns(true) |
|
| 1593 |
assert User.avatar_available? |
|
| 1594 |
with_settings :avatar_enabled => '0' do |
|
| 1595 |
assert_not User.avatar_available? |
|
| 1596 |
end |
|
| 1597 | ||
| 1598 |
Redmine::Thumbnail.stubs(:convert_available?).returns(false) |
|
| 1599 |
assert_not User.avatar_available? |
|
| 1600 |
end |
|
| 1601 | ||
| 1602 |
def test_update_avatar_should_reject_images_without_imagemagick |
|
| 1603 |
Redmine::Thumbnail.stubs(:convert_available?).returns(false) |
|
| 1604 |
assert_no_difference 'Attachment.count' do |
|
| 1605 |
assert_not @jsmith.update_avatar(uploaded_test_file('2010/11/101123161450_testfile_1.png', 'image/png'))
|
|
| 1606 |
end |
|
| 1607 |
end |
|
| 1608 | ||
| 1609 |
def test_update_avatar_should_reject_non_raster_images |
|
| 1610 |
assert_no_difference 'Attachment.count' do |
|
| 1611 |
assert_not update_avatar(@jsmith, 'testfile.svg') |
|
| 1612 |
end |
|
| 1613 |
assert_include :avatar, @jsmith.errors.attribute_names |
|
| 1614 |
end |
|
| 1615 | ||
| 1616 |
def test_update_avatar_should_replace_existing_avatar |
|
| 1617 |
set_tmp_attachments_directory |
|
| 1618 |
update_avatar(@jsmith, '2010/11/101123161450_testfile_1.png') |
|
| 1619 |
old_avatar_id = @jsmith.avatar_id |
|
| 1620 | ||
| 1621 |
update_avatar(@jsmith, '2010/12/101223161450_testfile_2.png') |
|
| 1622 |
assert_not_equal old_avatar_id, @jsmith.reload.avatar_id |
|
| 1623 |
assert_not Attachment.exists?(old_avatar_id) |
|
| 1624 |
end |
|
| 1625 | ||
| 1626 |
def test_delete_avatar_should_destroy_avatar |
|
| 1627 |
set_tmp_attachments_directory |
|
| 1628 |
update_avatar(@jsmith, '2010/11/101123161450_testfile_1.png') |
|
| 1629 |
avatar_id = @jsmith.avatar_id |
|
| 1630 | ||
| 1631 |
@jsmith.delete_avatar |
|
| 1632 |
assert_nil @jsmith.reload.avatar_id |
|
| 1633 |
assert_not Attachment.exists?(avatar_id) |
|
| 1634 |
end |
|
| 1635 | ||
| 1636 |
def test_destroy_should_destroy_avatar |
|
| 1637 |
set_tmp_attachments_directory |
|
| 1638 |
update_avatar(@jsmith, '2010/11/101123161450_testfile_1.png') |
|
| 1639 | ||
| 1640 |
assert_difference 'Attachment.count', -1 do |
|
| 1641 |
@jsmith.destroy |
|
| 1642 |
end |
|
| 1643 |
end |
|
| 1644 | ||
| 1645 |
private |
|
| 1646 | ||
| 1647 |
def update_avatar(user, name) |
|
| 1648 |
Redmine::Thumbnail.stubs(:convert_available?).returns(true) |
|
| 1649 |
user.update_avatar(uploaded_test_file(name, Redmine::MimeType.of(name))) |
|
| 1650 |
end |
|
| 1571 | 1651 |
end |