2013-04-11 16:04:20 -04:00
|
|
|
require_dependency 'user_destroyer'
|
2013-06-19 12:11:04 -04:00
|
|
|
require_dependency 'admin_user_index_query'
|
2017-04-04 13:59:22 -04:00
|
|
|
require_dependency 'admin_confirmation'
|
2013-04-11 16:04:20 -04:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
class Admin::UsersController < Admin::AdminController
|
|
|
|
|
2013-11-07 13:53:32 -05:00
|
|
|
before_filter :fetch_user, only: [:suspend,
|
|
|
|
:unsuspend,
|
2013-10-22 15:53:08 -04:00
|
|
|
:refresh_browsers,
|
2014-06-05 23:02:52 -04:00
|
|
|
:log_out,
|
2013-10-22 15:53:08 -04:00
|
|
|
:revoke_admin,
|
|
|
|
:grant_admin,
|
|
|
|
:revoke_moderation,
|
|
|
|
:grant_moderation,
|
|
|
|
:approve,
|
|
|
|
:activate,
|
|
|
|
:deactivate,
|
|
|
|
:block,
|
|
|
|
:unblock,
|
|
|
|
:trust_level,
|
2014-09-13 16:55:26 -04:00
|
|
|
:trust_level_lock,
|
2014-07-13 14:11:38 -04:00
|
|
|
:add_group,
|
|
|
|
:remove_group,
|
2014-02-10 16:59:36 -05:00
|
|
|
:primary_group,
|
2013-10-22 15:53:08 -04:00
|
|
|
:generate_api_key,
|
2015-03-06 16:44:54 -05:00
|
|
|
:revoke_api_key,
|
2016-05-06 13:34:33 -04:00
|
|
|
:anonymize,
|
|
|
|
:reset_bounce_score]
|
2013-05-31 11:41:40 -04:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
def index
|
2014-11-03 06:46:08 -05:00
|
|
|
users = ::AdminUserIndexQuery.new(params).find_users
|
|
|
|
|
|
|
|
if params[:show_emails] == "true"
|
|
|
|
guardian.can_see_emails = true
|
|
|
|
StaffActionLogger.new(current_user).log_show_emails(users)
|
|
|
|
end
|
|
|
|
|
2014-11-26 13:56:12 -05:00
|
|
|
render_serialized(users, AdminUserListSerializer)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def show
|
2015-09-26 09:56:36 -04:00
|
|
|
@user = User.find_by(id: params[:id])
|
2015-05-06 21:00:51 -04:00
|
|
|
raise Discourse::NotFound unless @user
|
2013-02-05 14:16:51 -05:00
|
|
|
render_serialized(@user, AdminDetailedUserSerializer, root: false)
|
|
|
|
end
|
|
|
|
|
2013-02-07 02:11:56 -05:00
|
|
|
def delete_all_posts
|
2014-05-06 09:41:59 -04:00
|
|
|
@user = User.find_by(id: params[:user_id])
|
2013-02-07 02:11:56 -05:00
|
|
|
@user.delete_all_posts!(guardian)
|
2017-01-10 16:45:36 -05:00
|
|
|
# staff action logs will have an entry for each post
|
2013-02-07 02:11:56 -05:00
|
|
|
render nothing: true
|
|
|
|
end
|
2013-04-04 12:59:44 -04:00
|
|
|
|
2013-11-07 13:53:32 -05:00
|
|
|
def suspend
|
|
|
|
guardian.ensure_can_suspend!(@user)
|
|
|
|
@user.suspended_till = params[:duration].to_i.days.from_now
|
|
|
|
@user.suspended_at = DateTime.now
|
2013-02-05 14:16:51 -05:00
|
|
|
@user.save!
|
2015-08-23 16:33:03 -04:00
|
|
|
@user.revoke_api_key
|
2013-11-07 13:53:32 -05:00
|
|
|
StaffActionLogger.new(current_user).log_user_suspend(@user, params[:reason])
|
2016-07-04 05:20:30 -04:00
|
|
|
@user.logged_out
|
2013-02-05 14:16:51 -05:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2013-11-07 13:53:32 -05:00
|
|
|
def unsuspend
|
|
|
|
guardian.ensure_can_suspend!(@user)
|
|
|
|
@user.suspended_till = nil
|
|
|
|
@user.suspended_at = nil
|
2013-02-05 14:16:51 -05:00
|
|
|
@user.save!
|
2013-11-07 13:53:32 -05:00
|
|
|
StaffActionLogger.new(current_user).log_user_unsuspend(@user)
|
2013-02-05 14:16:51 -05:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2014-06-05 23:02:52 -04:00
|
|
|
def log_out
|
2014-12-01 08:03:25 -05:00
|
|
|
if @user
|
2017-01-31 17:21:37 -05:00
|
|
|
@user.user_auth_tokens.destroy_all
|
2016-07-04 05:20:30 -04:00
|
|
|
@user.logged_out
|
2014-12-01 08:03:25 -05:00
|
|
|
render json: success_json
|
|
|
|
else
|
2017-07-27 21:20:09 -04:00
|
|
|
render json: { error: I18n.t('admin_js.admin.users.id_not_found') }, status: 404
|
2014-12-01 08:03:25 -05:00
|
|
|
end
|
2014-06-05 23:02:52 -04:00
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
def refresh_browsers
|
2014-04-28 13:46:28 -04:00
|
|
|
refresh_browser @user
|
2013-03-23 17:37:37 -04:00
|
|
|
render nothing: true
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def revoke_admin
|
2013-05-31 11:41:40 -04:00
|
|
|
guardian.ensure_can_revoke_admin!(@user)
|
|
|
|
@user.revoke_admin!
|
2016-01-27 04:38:16 -05:00
|
|
|
StaffActionLogger.new(current_user).log_revoke_admin(@user)
|
2013-02-05 14:16:51 -05:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2013-10-22 15:53:08 -04:00
|
|
|
def generate_api_key
|
|
|
|
api_key = @user.generate_api_key(current_user)
|
|
|
|
render_serialized(api_key, ApiKeySerializer)
|
|
|
|
end
|
|
|
|
|
|
|
|
def revoke_api_key
|
|
|
|
@user.revoke_api_key
|
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
def grant_admin
|
2017-04-04 13:59:22 -04:00
|
|
|
AdminConfirmation.new(@user, current_user).create_confirmation
|
|
|
|
render json: success_json
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2013-02-12 17:58:08 -05:00
|
|
|
def revoke_moderation
|
2013-05-31 11:41:40 -04:00
|
|
|
guardian.ensure_can_revoke_moderation!(@user)
|
|
|
|
@user.revoke_moderation!
|
2016-01-27 04:38:16 -05:00
|
|
|
StaffActionLogger.new(current_user).log_revoke_moderation(@user)
|
2013-02-12 17:58:08 -05:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
|
|
|
def grant_moderation
|
|
|
|
guardian.ensure_can_grant_moderation!(@user)
|
2013-05-06 00:49:56 -04:00
|
|
|
@user.grant_moderation!
|
2016-01-27 04:38:16 -05:00
|
|
|
StaffActionLogger.new(current_user).log_grant_moderation(@user)
|
2013-02-12 17:58:08 -05:00
|
|
|
render_serialized(@user, AdminUserSerializer)
|
|
|
|
end
|
|
|
|
|
2014-07-13 14:11:38 -04:00
|
|
|
def add_group
|
|
|
|
group = Group.find(params[:group_id].to_i)
|
|
|
|
return render_json_error group unless group && !group.automatic
|
2015-10-28 12:21:54 -04:00
|
|
|
|
2016-12-11 10:36:15 -05:00
|
|
|
group.add(@user)
|
|
|
|
GroupActionLogger.new(current_user, group).log_add_user_to_group(@user)
|
2015-10-28 12:21:54 -04:00
|
|
|
|
2014-07-13 14:11:38 -04:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
|
|
|
def remove_group
|
|
|
|
group = Group.find(params[:group_id].to_i)
|
|
|
|
return render_json_error group unless group && !group.automatic
|
2017-08-04 12:13:20 -04:00
|
|
|
|
2016-12-11 10:36:15 -05:00
|
|
|
group.remove(@user)
|
2017-01-04 04:37:23 -05:00
|
|
|
GroupActionLogger.new(current_user, group).log_remove_user_from_group(@user)
|
2017-08-04 12:13:20 -04:00
|
|
|
|
2014-07-13 14:11:38 -04:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2014-02-10 16:59:36 -05:00
|
|
|
def primary_group
|
|
|
|
guardian.ensure_can_change_primary_group!(@user)
|
2017-08-04 12:13:20 -04:00
|
|
|
|
|
|
|
if params[:primary_group_id].present?
|
|
|
|
primary_group_id = params[:primary_group_id].to_i
|
|
|
|
if group = Group.find(primary_group_id)
|
|
|
|
if group.user_ids.include?(@user.id)
|
|
|
|
@user.primary_group_id = primary_group_id
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
@user.primary_group_id = nil
|
2017-05-17 12:42:04 -04:00
|
|
|
end
|
2017-08-04 12:13:20 -04:00
|
|
|
|
|
|
|
@user.save!
|
|
|
|
|
2014-02-10 16:59:36 -05:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2013-07-03 04:27:40 -04:00
|
|
|
def trust_level
|
|
|
|
guardian.ensure_can_change_trust_level!(@user)
|
2014-09-29 23:12:33 -04:00
|
|
|
level = params[:level].to_i
|
|
|
|
|
2017-07-27 21:20:09 -04:00
|
|
|
if !@user.trust_level_locked && [0, 1, 2].include?(level) && Promotion.send("tl#{level + 1}_met?", @user)
|
2014-09-29 23:12:33 -04:00
|
|
|
@user.trust_level_locked = true
|
|
|
|
@user.save
|
|
|
|
end
|
|
|
|
|
|
|
|
if !@user.trust_level_locked && level == 3 && Promotion.tl3_lost?(@user)
|
|
|
|
@user.trust_level_locked = true
|
|
|
|
@user.save
|
|
|
|
end
|
|
|
|
|
|
|
|
@user.change_trust_level!(level, log_action_for: current_user)
|
2014-06-16 20:46:30 -04:00
|
|
|
|
2013-07-03 04:27:40 -04:00
|
|
|
render_serialized(@user, AdminUserSerializer)
|
2014-07-29 15:54:20 -04:00
|
|
|
rescue Discourse::InvalidAccess => e
|
|
|
|
render_json_error(e.message)
|
2013-07-03 04:27:40 -04:00
|
|
|
end
|
|
|
|
|
2014-09-13 16:55:26 -04:00
|
|
|
def trust_level_lock
|
|
|
|
guardian.ensure_can_change_trust_level!(@user)
|
|
|
|
|
2014-09-29 23:12:33 -04:00
|
|
|
new_lock = params[:locked].to_s
|
|
|
|
unless new_lock =~ /true|false/
|
2015-05-26 09:16:55 -04:00
|
|
|
return render_json_error I18n.t('errors.invalid_boolean')
|
2014-09-13 16:55:26 -04:00
|
|
|
end
|
|
|
|
|
2014-09-29 23:12:33 -04:00
|
|
|
@user.trust_level_locked = new_lock == "true"
|
2014-09-13 16:55:26 -04:00
|
|
|
@user.save
|
2014-09-29 23:12:33 -04:00
|
|
|
|
2017-01-10 16:45:36 -05:00
|
|
|
StaffActionLogger.new(current_user).log_lock_trust_level(@user)
|
|
|
|
|
2014-09-29 23:12:33 -04:00
|
|
|
unless @user.trust_level_locked
|
|
|
|
p = Promotion.new(@user)
|
2017-07-27 21:20:09 -04:00
|
|
|
2.times { p.review }
|
2014-09-29 23:12:33 -04:00
|
|
|
p.review_tl2
|
|
|
|
if @user.trust_level == 3 && Promotion.tl3_lost?(@user)
|
|
|
|
@user.change_trust_level!(2, log_action_for: current_user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-09-13 16:55:26 -04:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
def approve
|
|
|
|
guardian.ensure_can_approve!(@user)
|
|
|
|
@user.approve(current_user)
|
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
|
|
|
def approve_bulk
|
|
|
|
User.where(id: params[:users]).each do |u|
|
|
|
|
u.approve(current_user) if guardian.can_approve?(u)
|
|
|
|
end
|
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2013-05-07 21:58:34 -04:00
|
|
|
def activate
|
|
|
|
guardian.ensure_can_activate!(@user)
|
2017-07-31 12:54:09 -04:00
|
|
|
# ensure there is an active email token
|
|
|
|
@user.email_tokens.create(email: @user.email) unless @user.email_tokens.active.exists?
|
2013-05-07 21:58:34 -04:00
|
|
|
@user.activate
|
2017-01-10 16:45:36 -05:00
|
|
|
StaffActionLogger.new(current_user).log_user_activate(@user, I18n.t('user.activated_by_staff'))
|
2014-12-08 13:16:57 -05:00
|
|
|
render json: success_json
|
2013-05-07 21:58:34 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def deactivate
|
|
|
|
guardian.ensure_can_deactivate!(@user)
|
|
|
|
@user.deactivate
|
2017-01-10 16:45:36 -05:00
|
|
|
StaffActionLogger.new(current_user).log_user_deactivate(@user, I18n.t('user.deactivated_by_staff'))
|
2014-04-28 13:46:28 -04:00
|
|
|
refresh_browser @user
|
2013-05-07 21:58:34 -04:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2013-05-31 11:41:40 -04:00
|
|
|
def block
|
|
|
|
guardian.ensure_can_block_user! @user
|
2016-01-14 15:20:26 -05:00
|
|
|
UserBlocker.block(@user, current_user, keep_posts: true)
|
2013-05-31 11:41:40 -04:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
|
|
|
def unblock
|
|
|
|
guardian.ensure_can_unblock_user! @user
|
2013-07-02 14:42:30 -04:00
|
|
|
UserBlocker.unblock(@user, current_user)
|
2013-05-31 11:41:40 -04:00
|
|
|
render nothing: true
|
|
|
|
end
|
|
|
|
|
2013-08-16 11:42:24 -04:00
|
|
|
def reject_bulk
|
|
|
|
success_count = 0
|
2014-10-20 10:59:06 -04:00
|
|
|
d = UserDestroyer.new(current_user)
|
|
|
|
|
2013-08-16 11:42:24 -04:00
|
|
|
User.where(id: params[:users]).each do |u|
|
2017-07-27 21:20:09 -04:00
|
|
|
success_count += 1 if guardian.can_delete_user?(u) && d.destroy(u, params.slice(:context)) rescue UserDestroyer::PostsExistError
|
2013-08-16 11:42:24 -04:00
|
|
|
end
|
2014-10-20 10:59:06 -04:00
|
|
|
|
|
|
|
render json: {
|
|
|
|
success: success_count,
|
|
|
|
failed: (params[:users].try(:size) || 0) - success_count
|
|
|
|
}
|
2013-08-16 11:42:24 -04:00
|
|
|
end
|
|
|
|
|
2013-04-11 16:04:20 -04:00
|
|
|
def destroy
|
2014-07-28 13:17:37 -04:00
|
|
|
user = User.find_by(id: params[:id].to_i)
|
2013-04-11 16:04:20 -04:00
|
|
|
guardian.ensure_can_delete_user!(user)
|
2013-07-24 13:48:55 -04:00
|
|
|
begin
|
2014-10-20 10:59:06 -04:00
|
|
|
options = params.slice(:delete_posts, :block_email, :block_urls, :block_ip, :context, :delete_as_spammer)
|
|
|
|
if UserDestroyer.new(current_user).destroy(user, options)
|
|
|
|
render json: { deleted: true }
|
2013-07-24 13:48:55 -04:00
|
|
|
else
|
2014-10-20 10:59:06 -04:00
|
|
|
render json: {
|
|
|
|
deleted: false,
|
|
|
|
user: AdminDetailedUserSerializer.new(user, root: false).as_json
|
|
|
|
}
|
2013-07-24 13:48:55 -04:00
|
|
|
end
|
|
|
|
rescue UserDestroyer::PostsExistError
|
|
|
|
raise Discourse::InvalidAccess.new("User #{user.username} has #{user.post_count} posts, so can't be deleted.")
|
2013-04-11 16:04:20 -04:00
|
|
|
end
|
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2014-03-19 10:27:21 -04:00
|
|
|
def badges
|
|
|
|
end
|
|
|
|
|
2014-09-24 20:19:26 -04:00
|
|
|
def tl3_requirements
|
2014-01-23 16:40:10 -05:00
|
|
|
end
|
|
|
|
|
2014-07-07 16:18:18 -04:00
|
|
|
def ip_info
|
|
|
|
params.require(:ip)
|
|
|
|
ip = params[:ip]
|
|
|
|
|
|
|
|
# should we cache results in redis?
|
2017-08-21 14:18:40 -04:00
|
|
|
location = Excon.get("https://ipinfo.io/#{ip}/json", read_timeout: 10, connect_timeout: 10).body rescue nil
|
2014-07-07 16:18:18 -04:00
|
|
|
|
|
|
|
render json: location
|
|
|
|
end
|
2013-05-31 11:41:40 -04:00
|
|
|
|
2014-10-27 20:25:02 -04:00
|
|
|
def sync_sso
|
2014-11-20 13:59:20 -05:00
|
|
|
return render nothing: true, status: 404 unless SiteSetting.enable_sso
|
2014-10-27 20:25:02 -04:00
|
|
|
|
2014-10-30 06:00:44 -04:00
|
|
|
sso = DiscourseSingleSignOn.parse("sso=#{params[:sso]}&sig=#{params[:sig]}")
|
2014-10-27 20:25:02 -04:00
|
|
|
|
2016-03-26 01:28:49 -04:00
|
|
|
begin
|
|
|
|
user = sso.lookup_or_create_user
|
|
|
|
render_serialized(user, AdminDetailedUserSerializer, root: false)
|
|
|
|
rescue ActiveRecord::RecordInvalid => ex
|
|
|
|
render json: failed_json.merge(message: ex.message), status: 403
|
|
|
|
end
|
2014-10-27 20:25:02 -04:00
|
|
|
end
|
|
|
|
|
2014-11-20 13:59:20 -05:00
|
|
|
def delete_other_accounts_with_same_ip
|
|
|
|
params.require(:ip)
|
|
|
|
params.require(:exclude)
|
|
|
|
params.require(:order)
|
|
|
|
|
|
|
|
user_destroyer = UserDestroyer.new(current_user)
|
|
|
|
options = { delete_posts: true, block_email: true, block_urls: true, block_ip: true, delete_as_spammer: true }
|
|
|
|
|
2014-11-24 13:34:04 -05:00
|
|
|
AdminUserIndexQuery.new(params).find_users(50).each do |user|
|
2014-11-20 13:59:20 -05:00
|
|
|
user_destroyer.destroy(user, options) rescue nil
|
|
|
|
end
|
|
|
|
|
|
|
|
render json: success_json
|
|
|
|
end
|
|
|
|
|
2014-11-24 13:34:04 -05:00
|
|
|
def total_other_accounts_with_same_ip
|
|
|
|
params.require(:ip)
|
|
|
|
params.require(:exclude)
|
|
|
|
params.require(:order)
|
|
|
|
|
|
|
|
render json: { total: AdminUserIndexQuery.new(params).count_users }
|
|
|
|
end
|
|
|
|
|
2014-11-23 23:42:39 -05:00
|
|
|
def invite_admin
|
2017-04-04 13:59:22 -04:00
|
|
|
raise Discourse::InvalidAccess.new unless is_api?
|
2014-11-23 23:42:39 -05:00
|
|
|
|
|
|
|
email = params[:email]
|
|
|
|
unless user = User.find_by_email(email)
|
|
|
|
name = params[:name] if params[:name].present?
|
|
|
|
username = params[:username] if params[:username].present?
|
|
|
|
|
|
|
|
user = User.new(email: email)
|
|
|
|
user.password = SecureRandom.hex
|
|
|
|
user.username = UserNameSuggester.suggest(username || name || email)
|
|
|
|
user.name = User.suggest_name(name || username || email)
|
|
|
|
end
|
|
|
|
|
|
|
|
user.active = true
|
|
|
|
user.save!
|
|
|
|
user.grant_admin!
|
|
|
|
user.change_trust_level!(4)
|
2017-07-27 21:20:09 -04:00
|
|
|
user.email_tokens.update_all confirmed: true
|
2014-11-23 23:42:39 -05:00
|
|
|
|
|
|
|
email_token = user.email_tokens.create(email: user.email)
|
2015-01-02 15:48:34 -05:00
|
|
|
|
|
|
|
unless params[:send_email] == '0' || params[:send_email] == 'false'
|
2017-07-27 21:20:09 -04:00
|
|
|
Jobs.enqueue(:critical_user_email,
|
2014-11-23 23:42:39 -05:00
|
|
|
type: :account_created,
|
|
|
|
user_id: user.id,
|
|
|
|
email_token: email_token.token)
|
2015-01-02 15:48:34 -05:00
|
|
|
end
|
2014-11-23 23:42:39 -05:00
|
|
|
|
2017-03-28 14:27:54 -04:00
|
|
|
render json: success_json.merge!(
|
|
|
|
password_url: "#{Discourse.base_url}#{password_reset_token_path(token: email_token.token)}"
|
|
|
|
)
|
2014-11-23 23:42:39 -05:00
|
|
|
|
|
|
|
end
|
|
|
|
|
2015-03-06 16:44:54 -05:00
|
|
|
def anonymize
|
|
|
|
guardian.ensure_can_anonymize_user!(@user)
|
|
|
|
if user = UserAnonymizer.new(@user, current_user).make_anonymous
|
|
|
|
render json: success_json.merge(username: user.username)
|
|
|
|
else
|
|
|
|
render json: failed_json.merge(user: AdminDetailedUserSerializer.new(user, root: false).as_json)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-05-06 13:34:33 -04:00
|
|
|
def reset_bounce_score
|
|
|
|
guardian.ensure_can_reset_bounce_score!(@user)
|
2017-02-20 04:37:01 -05:00
|
|
|
@user.user_stat&.reset_bounce_score!
|
2016-05-06 13:34:33 -04:00
|
|
|
render json: success_json
|
|
|
|
end
|
|
|
|
|
2013-05-31 11:41:40 -04:00
|
|
|
private
|
|
|
|
|
|
|
|
def fetch_user
|
2014-05-06 09:41:59 -04:00
|
|
|
@user = User.find_by(id: params[:user_id])
|
2013-05-31 11:41:40 -04:00
|
|
|
end
|
|
|
|
|
2014-04-28 13:46:28 -04:00
|
|
|
def refresh_browser(user)
|
2015-05-03 22:21:00 -04:00
|
|
|
MessageBus.publish "/file-change", ["refresh"], user_ids: [user.id]
|
2014-04-28 13:46:28 -04:00
|
|
|
end
|
|
|
|
|
2013-04-11 16:04:20 -04:00
|
|
|
end
|