otwarchive-symphonyarchive/app/controllers/challenge_signups_controller.rb
2026-03-11 22:22:11 +00:00

396 lines
14 KiB
Ruby

# For exporting to Excel CSV format
require 'csv'
class ChallengeSignupsController < ApplicationController
include ExportsHelper
before_action :users_only, except: [:summary]
before_action :load_collection, except: [:index]
before_action :load_challenge, except: [:index]
before_action :load_signup_from_id, only: [:show, :edit, :update, :destroy, :confirm_delete]
before_action :allowed_to_destroy, only: [:destroy, :confirm_delete]
before_action :signup_owner_only, only: [:edit, :update]
before_action :maintainer_or_signup_owner_only, only: [:show]
before_action :check_signup_open, only: [:new, :create, :edit, :update]
before_action :check_pseud_ownership, only: [:create, :update]
before_action :check_signup_in_collection, only: [:show, :edit, :update, :destroy, :confirm_delete]
def load_challenge
@challenge = @collection.challenge
no_challenge and return unless @challenge
end
def no_challenge
flash[:error] = ts("What challenge did you want to sign up for?")
redirect_to collection_path(@collection) rescue redirect_to '/'
false
end
def check_signup_open
signup_closed and return unless (@challenge.signup_open || @collection.user_is_maintainer?(current_user))
end
def signup_closed
flash[:error] = ts("Sign-up is currently closed: please contact a moderator for help.")
redirect_to @collection rescue redirect_to '/'
false
end
def signup_closed_owner?
@collection.challenge_type == "GiftExchange" && !@challenge.signup_open && @collection.user_is_owner?(current_user)
end
def signup_owner_only
not_signup_owner and return unless @challenge_signup.pseud.user == current_user || signup_closed_owner?
end
def maintainer_or_signup_owner_only
not_allowed(@collection) and return unless (@challenge_signup.pseud.user == current_user || @collection.user_is_maintainer?(current_user))
end
def not_signup_owner
flash[:error] = ts("You can't edit someone else's sign-up!")
redirect_to @collection
false
end
def allowed_to_destroy
@challenge_signup.user_allowed_to_destroy?(current_user) || not_allowed(@collection)
end
def load_signup_from_id
@challenge_signup = ChallengeSignup.find(params[:id])
no_signup and return unless @challenge_signup
end
def no_signup
flash[:error] = ts("What sign-up did you want to work on?")
redirect_to collection_path(@collection) rescue redirect_to '/'
false
end
def check_pseud_ownership
if params[:challenge_signup][:pseud_id] && (pseud = Pseud.find(params[:challenge_signup][:pseud_id]))
# either you have to own the pseud, OR you have to be a mod editing after signups are closed and NOT changing the pseud
unless current_user.pseuds.include?(pseud) || (@challenge_signup && @challenge_signup.pseud == pseud && signup_closed_owner?)
flash[:error] = ts("You can't sign up with that pseud.")
redirect_to root_path and return
end
end
end
def check_signup_in_collection
unless @challenge_signup.collection_id == @collection.id
flash[:error] = ts("Sorry, that sign-up isn't associated with that collection.")
redirect_to @collection
end
end
#### ACTIONS
def index
if params[:user_id] && (@user = User.find_by(login: params[:user_id]))
if current_user == @user
@challenge_signups = @user.challenge_signups.order_by_date
render action: :index and return
else
flash[:error] = ts("You aren't allowed to see that user's sign-ups.")
redirect_to '/' and return
end
else
load_collection
load_challenge if @collection
return false unless @challenge
end
# using respond_to in order to provide Excel output
# see ExportsHelper for export_csv method
respond_to do |format|
format.html {
if @challenge.user_allowed_to_see_signups?(current_user)
@challenge_signups = @collection.signups.joins(:pseud)
if params[:query]
@query = params[:query]
@challenge_signups = @challenge_signups.where("pseuds.name LIKE ?", '%' + params[:query] + '%')
end
@challenge_signups = @challenge_signups.order("pseuds.name").paginate(page: params[:page], per_page: ArchiveConfig.ITEMS_PER_PAGE)
elsif params[:user_id] && (@user = User.find_by(login: params[:user_id]))
@challenge_signups = @collection.signups.by_user(current_user)
else
not_allowed(@collection)
end
}
format.csv {
if (@collection.gift_exchange? && @challenge.user_allowed_to_see_signups?(current_user)) ||
(@collection.prompt_meme? && @collection.user_is_maintainer?(current_user))
csv_data = self.send("#{@challenge.class.name.underscore}_to_csv")
filename = "#{@collection.name}_signups_#{Time.now.strftime('%Y-%m-%d-%H%M')}.csv"
send_csv_data(csv_data, filename)
else
flash[:error] = ts("You aren't allowed to see the CSV summary.")
redirect_to collection_path(@collection) rescue redirect_to '/' and return
end
}
end
end
def summary
@summary = ChallengeSignupSummary.new(@collection)
if @collection.signups.count < (ArchiveConfig.ANONYMOUS_THRESHOLD_COUNT/2)
flash.now[:notice] = ts("Summary does not appear until at least %{count} sign-ups have been made!", count: ((ArchiveConfig.ANONYMOUS_THRESHOLD_COUNT/2)))
elsif @collection.signups.count > ArchiveConfig.MAX_SIGNUPS_FOR_LIVE_SUMMARY
# too many signups in this collection to show the summary page "live"
modification_time = @summary.cached_time
# The time is always written alongside the cache, so if the time is
# missing, then the cache must be missing as well -- and we want to
# generate it. We also want to generate it if signups are open and it was
# last generated more than an hour ago.
if modification_time.nil? ||
(@collection.challenge.signup_open? && modification_time < 1.hour.ago)
# Touch the cache so that we don't try to generate the summary a second
# time on subsequent page loads.
@summary.touch_cache
# Generate the cache of the summary in the background.
@summary.enqueue_for_generation
end
else
# generate it on the fly
@tag_type = @summary.tag_type
@summary_tags = @summary.summary
@generated_live = true
end
end
def show
unless @challenge_signup.valid?
flash[:error] = ts("This sign-up is invalid. Please check your sign-ups for a duplicate or edit to fix any other problems.")
end
end
protected
def build_prompts
notice = ""
@challenge.class::PROMPT_TYPES.each do |prompt_type|
num_to_build = params["num_#{prompt_type}"] ? params["num_#{prompt_type}"].to_i : @challenge.required(prompt_type)
if num_to_build < @challenge.required(prompt_type)
notice += ts("You must submit at least %{required} #{prompt_type}. ", required: @challenge.required(prompt_type))
num_to_build = @challenge.required(prompt_type)
elsif num_to_build > @challenge.allowed(prompt_type)
notice += ts("You can only submit up to %{allowed} #{prompt_type}. ", allowed: @challenge.allowed(prompt_type))
num_to_build = @challenge.allowed(prompt_type)
elsif params["num_#{prompt_type}"]
notice += ts("Set up %{num} #{prompt_type.pluralize}. ", num: num_to_build)
end
num_existing = @challenge_signup.send(prompt_type).count
num_existing.upto(num_to_build-1) do
@challenge_signup.send(prompt_type).build
end
end
unless notice.blank?
flash[:notice] = notice
end
end
public
def new
if (@challenge_signup = ChallengeSignup.in_collection(@collection).by_user(current_user).first)
flash[:notice] = ts("You are already signed up for this challenge. You can edit your sign-up below.")
redirect_to edit_collection_signup_path(@collection, @challenge_signup)
else
@challenge_signup = ChallengeSignup.new
build_prompts
end
end
def edit
build_prompts
end
def create
@challenge_signup = ChallengeSignup.new(challenge_signup_params)
@challenge_signup.pseud = current_user.default_pseud unless @challenge_signup.pseud
@challenge_signup.collection = @collection
# we check validity first to prevent saving tag sets if invalid
if @challenge_signup.valid? && @challenge_signup.save
flash[:notice] = ts('Sign-up was successfully created.')
redirect_to collection_signup_path(@collection, @challenge_signup)
else
render action: :new
end
end
def update
if @challenge_signup.update(challenge_signup_params)
flash[:notice] = ts('Sign-up was successfully updated.')
redirect_to collection_signup_path(@collection, @challenge_signup)
else
render action: :edit
end
end
def confirm_delete
end
def destroy
unless @challenge.signup_open || @collection.user_is_maintainer?(current_user)
flash[:error] = ts("You cannot delete your sign-up after sign-ups are closed. Please contact a moderator for help.")
else
@challenge_signup.destroy
flash[:notice] = ts("Challenge sign-up was deleted.")
end
if @collection.user_is_maintainer?(current_user) && !@collection.prompt_meme?
redirect_to collection_signups_path(@collection)
elsif @collection.prompt_meme?
redirect_to collection_requests_path(@collection)
else
redirect_to @collection
end
end
protected
def request_to_array(type, request)
any_types = TagSet::TAG_TYPES.select {|type| request && request.send("any_#{type}")}
any_types.map! { |type| ts("Any %{type}", type: type.capitalize) }
tags = request.nil? ? [] : request.tag_set.tags.map {|tag| tag.name}
rarray = [(tags + any_types).join(", ")]
if @challenge.send("#{type}_restriction").optional_tags_allowed
rarray << (request.nil? ? "" : request.optional_tag_set.tags.map {|tag| tag.name}.join(", "))
end
if @challenge.send("#{type}_restriction").title_allowed
rarray << (request.nil? ? "" : sanitize_field(request, :title))
end
if @challenge.send("#{type}_restriction").description_allowed
description = (request.nil? ? "" : sanitize_field(request, :description))
# Didn't find a way to get Excel 2007 to accept line breaks
# withing a field; not even when the row delimiter is set to
# \r\n and linebreaks within the field are only \n. :-(
#
# Thus stripping linebreaks.
rarray << description.gsub(/[\n\r]/, " ")
end
rarray << (request.nil? ? "" : request.url) if
@challenge.send("#{type}_restriction").url_allowed
return rarray
end
def gift_exchange_to_csv
header = ["Pseud", "Email", "Sign-up URL"]
%w(request offer).each do |type|
@challenge.send("#{type.pluralize}_num_allowed").times do |i|
header << "#{type.capitalize} #{i+1} Tags"
header << "#{type.capitalize} #{i+1} Optional Tags" if
@challenge.send("#{type}_restriction").optional_tags_allowed
header << "#{type.capitalize} #{i+1} Title" if
@challenge.send("#{type}_restriction").title_allowed
header << "#{type.capitalize} #{i+1} Description" if
@challenge.send("#{type}_restriction").description_allowed
header << "#{type.capitalize} #{i+1} URL" if
@challenge.send("#{type}_restriction").url_allowed
end
end
csv_array = []
csv_array << header
@collection.signups.each do |signup|
row = [signup.pseud.name, signup.pseud.user.email,
collection_signup_url(@collection, signup)]
%w(request offer).each do |type|
@challenge.send("#{type.pluralize}_num_allowed").times do |i|
row += request_to_array(type, signup.send(type.pluralize)[i])
end
end
csv_array << row
end
csv_array
end
def prompt_meme_to_csv
header = ["Pseud", "Sign-up URL", "Tags"]
header << "Optional Tags" if @challenge.request_restriction.optional_tags_allowed
header << "Title" if @challenge.request_restriction.title_allowed
header << "Description" if @challenge.request_restriction.description_allowed
header << "URL" if @challenge.request_restriction.url_allowed
csv_array = []
csv_array << header
@collection.prompts.where(type: "Request").each do |request|
row =
if request.anonymous?
["(Anonymous)", ""]
else
[request.challenge_signup.pseud.name,
collection_signup_url(@collection, request.challenge_signup)]
end
csv_array << (row + request_to_array("request", request))
end
csv_array
end
private
def challenge_signup_params
params.require(:challenge_signup).permit(
:pseud_id,
requests_attributes: nested_prompt_params,
offers_attributes: nested_prompt_params
)
end
def nested_prompt_params
[
:id,
:title,
:url,
:any_fandom,
:any_character,
:any_relationship,
:any_freeform,
:any_category,
:any_rating,
:any_archive_warning,
:anonymous,
:description,
:_destroy,
tag_set_attributes: [
:id,
:updated_at,
:character_tagnames,
:relationship_tagnames,
:freeform_tagnames,
:category_tagnames,
:rating_tagnames,
:archive_warning_tagnames,
:fandom_tagnames,
character_tagnames: [],
relationship_tagnames: [],
freeform_tagnames: [],
category_tagnames: [],
rating_tagnames: [],
archive_warning_tagnames: [],
fandom_tagnames: [],
],
optional_tag_set_attributes: [
:tagnames
]
]
end
end