module OpenConferenceWare class ProposalsController < ApplicationController before_filter :authentication_required, only: [:edit, :update, :destroy, :speaker_confirm, :speaker_decline, :proposal_login_required] before_filter :assert_current_event_or_redirect before_filter :assert_proposal_status_published, only: [:sessions_index, :sessions_index_terse, :session_show] before_filter :assert_schedule_published, only: [:schedule] before_filter :normalize_event_path_or_redirect, only: [:index, :sessions_index, :schedule] before_filter :assert_anonymous_proposals, only: [:new, :create] before_filter :assert_accepting_proposals, only: [:new, :create] before_filter :assign_proposal_and_event, only: [:show, :session_show, :edit, :update, :destroy, :speaker_confirm, :speaker_decline] before_filter :assert_record_ownership, only: [:edit, :update, :destroy, :speaker_confirm, :speaker_decline] before_filter :assert_user_complete_profile, only: [:new, :edit, :update] before_filter :assign_proposals_breadcrumb MAX_FEED_ITEMS = 50 SESSION_RELATED_ACTIONS = ['sessions_index', 'session_show', 'schedule'] def proposals_or_sessions if @event && @event.proposal_status_published? redirect_to event_sessions_path(@event) else redirect_to event_proposals_path(@event) end end # GET /proposals # GET /proposals.xml def index warn_about_incomplete_event @kind = :proposals assign_prefetched_hashes @proposals = Defer { @proposals_hash.values } unless params[:sort] params[:sort] = "submitted_at" params[:dir] = "desc" end respond_to do |format| format.html { add_breadcrumb @event.title, event_proposals_path(@event) } format.xml { render xml: @proposals } format.json { render json: @proposals } format.atom { # index.atom.builder if @event_assignment == :assigned_to_param @cache_key = "proposals_atom,event_#{@event.id}" @proposals = Defer { @event.populated_proposals(:proposals).reorder("submitted_at desc").limit(MAX_FEED_ITEMS) } else @cache_key = "proposals_atom,all" @proposals = Defer { Proposal.populated.reorder("submitted_at desc").limit(MAX_FEED_ITEMS) } end } format.csv { records = @event.populated_proposals(@kind).includes(:comments) if admin? render csv: records, style: :admin else if schedule_visible? render csv: records, style: :schedule else render csv: records end end } end end def sessions_index @kind = :sessions assign_prefetched_hashes @proposals = Defer { @sessions_hash.values } params[:sort] ||= "track" respond_to do |format| format.html { add_breadcrumb @event.title, event_proposals_path(@event) render action: "index" } format.xml { render xml: @proposals } format.json { render json: @proposals } end end def sessions_index_terse assign_prefetched_hashes end def schedule page_title 'Schedule' @schedule = Defer { Schedule.new(@event, admin?) } assign_prefetched_hashes respond_to do |format| format.html { @view_cache_key = "schedule,event_#{@event.id},admin_#{admin?}" } format.json { render json: @schedule } format.xml { ### render xml: @schedule render xml: {error: "XML output of schedule not yet supported"}, status: :unprocessable_entity } format.ics { view_cache_key = "schedule,event_#{@event.id}.ics" data = Rails.cache.fetch(view_cache_key) { Proposal.to_icalendar( @schedule.items, title: "#{@event.title}", url_helper: lambda {|item| session_url(item)}) } render text: data } end end def session_show # @proposal and @event set via #assign_proposal_and_event filter @kind = :session unless @proposal.confirmed? @redirector = lambda { flash[:failure] = "This proposal is not a session." return redirect_to( proposal_path(@proposal) ) } end return base_show end # GET /proposals/1 # GET /proposals/1.xml def show # @proposal and @event set via #assign_proposal_and_event filter @kind = :proposal if @event.proposal_status_published? && @proposal.confirmed? @redirector = lambda { # flash[:notice] = "This proposal has been accepted as a session." flash.keep return redirect_to( session_path(@proposal) ) } end return base_show end # GET /proposals/new # GET /proposals/new.xml def new add_breadcrumb @event.title, event_proposals_path(@event) add_breadcrumb "Create a proposal", new_event_proposal_path(@event) @proposal = Proposal.new(agreement: false) # Set default track if only one was found. if event_tracks? && @event.tracks.size == 1 @proposal.track = @event.tracks.first end # Set default session_type if only one was found. if event_session_types? && @event.session_types.size == 1 @proposal.session_type = @event.session_types.first end if logged_in? @proposal.presenter = current_user.fullname @proposal.add_user(current_user) end @proposal.email = current_email respond_to do |format| format.html # new.html.erb format.xml { render xml: @proposal } format.json { render json: @proposal } end end # GET /proposals/1/edit def edit # @proposal set via #assign_proposal filter @event = @proposal.event add_breadcrumb @event.title, event_proposals_path(@event) add_breadcrumb @proposal.title, proposal_path(@proposal) end # POST /proposals # POST /proposals.xml def create @proposal = @event.proposals.new(proposal_params) @proposal.add_user(current_user) if logged_in? @proposal.transition = transition_from_params if admin? manage_speakers_on_submit respond_to do |format| if params[:speaker_submit].blank? && params[:preview].nil? && @proposal.save format.html { # Display a page thanking users for submitting a proposal and telling them what to do next. page_title "Thank You!" } format.xml { render xml: @proposal, status: :created, location: @proposal } format.json { render json: @proposal, status: :created, location: @proposal } else @proposal.valid? if params[:preview] format.html { render action: "new" } format.xml { render xml: @proposal.errors, status: :unprocessable_entity } format.json { render json: @proposal.errors, status: :unprocessable_entity } end end end # PUT /proposals/1 # PUT /proposals/1.xml def update # @proposal and @event set via #assign_proposal_and_event filter # If proposal title editing is locked, prevent non-admin from modifying title. if params[:proposal] && @event.proposal_titles_locked? && ! admin? params[:proposal].delete(:title) end if params[:proposal][:start_time] && admin? if params[:proposal][:start_time][:date].blank? || params[:proposal][:start_time][:hour].blank? || params[:proposal][:start_time][:minute].blank? @proposal.start_time = nil else @proposal.start_time = "#{params[:proposal][:start_time][:date]} #{params[:proposal][:start_time][:hour]}:#{params[:proposal][:start_time][:minute]}" end end @proposal.assign_attributes(proposal_params) add_breadcrumb @event.title, event_proposals_path(@event) add_breadcrumb @proposal.title, proposal_path(@proposal) manage_speakers_on_submit respond_to do |format| if params[:speaker_submit].blank? && params[:preview].nil? && @proposal.save @proposal.transition = transition_from_params if admin? format.html { flash[:success] = 'Updated proposal.' redirect_to(@proposal) } format.xml { head :ok } format.json { render( json: { proposal_status: @proposal.status, _transition_control_html: render_to_string(partial: '/open_conference_ware/proposals/transition_control', formats: [:html]) }, status: :ok ) } else @proposal.valid? if params[:preview] format.html { render action: "edit" } format.xml { render xml: @proposal.errors, status: :unprocessable_entity } format.json { render json: @proposal.errors, status: :unprocessable_entity } end end end # DELETE /proposals/1 # DELETE /proposals/1.xml def destroy # @proposal and @event set via #assign_proposal_and_event filter @proposal.destroy flash[:success] = "Destroyed proposal: #{@proposal.title}" respond_to do |format| format.html { redirect_to(event_proposals_path(@proposal.event)) } format.xml { head :ok } format.json { head :ok } end end def manage_speakers @proposal = get_proposal_for_speaker_manager(params[:id], params[:speakers]) if params[:add] user = User.find(params[:add]) @proposal.add_user(user) elsif params[:remove] user = User.find(params[:remove]) @proposal.remove_user(user) end respond_to do |format| format.html { render partial: "manage_speakers", layout: false } end end def search_speakers @proposal = get_proposal_for_speaker_manager(params[:id], params[:speakers]) @matches = get_speaker_matches(params[:search]) respond_to do |format| format.html { render partial: "search_speakers", layout: false } end end def stats # Uses @event end # GET /proposals/1/login def proposal_login_required return redirect_to(proposal_path(params[:proposal_id])) end def speaker_confirm # @proposal and @event set via #assign_proposal_and_event filter if current_user_is_proposal_speaker? and @proposal.confirm! flash[:success] = 'Updated proposal. Thank you for confirming!' end redirect_to(@proposal) end def speaker_decline # @proposal and @event set via #assign_proposal_and_event filter if current_user_is_proposal_speaker? and @proposal.decline! flash[:success] = 'Updated proposal.' end redirect_to(@proposal) end protected def proposal_params permitted = [ :presenter, :affiliation, :email, :website, :biography, :title, :description, :excerpt, :agreement, :note_to_organizers, :track_id, :session_type_id, :speaking_experience, :audience_level, :tag_list] permitted += [:status, :room_id, :start_time, :audio_url] if admin? params.require(:proposal).permit(permitted) end # Is this event accepting proposals? If not, redirect with a warning. def assert_accepting_proposals unless accepting_proposals? || admin? flash[:failure] = Snippet.content_for(:proposals_not_accepted_error) redirect_to @event ? event_proposals_path(@event) : proposals_path end end # Ensure that anonymous users are allowed to add proposals def assert_anonymous_proposals if logged_in? return false # Logged in users can always create else if anonymous_proposals? return false # Anonymous proposals are allowed else flash[:notice] = "Please sign in so you can create and manage proposals." store_location return redirect_to(sign_in_path) end end end # Return the proposal and its assignment status for this request. The status # can be: # * :assigned_via_param # * :invalid_proposal # * :invalid_event def get_proposal_and_assignment_status if proposal = Proposal.find(params[:id].to_i) rescue nil if proposal.event return [proposal, :assigned_via_param] else return [proposal, :invalid_event] end else return [proposal, :invalid_proposal] end end def get_proposal_for_speaker_manager(proposal_id, speaker_ids_string) if proposal_id.blank? || proposal_id == "new_record" proposal = Proposal.new speaker_ids_string.split(',').each do |speaker| proposal.add_user(speaker) end else proposal = Proposal.find(proposal_id) end return proposal end # Assign @proposal and @event from parameters, or redirect with warnings. def assign_proposal_and_event @proposal, @proposal_assignment_status = get_proposal_and_assignment_status() case @proposal_assignment_status when :assigned_via_param @event = @proposal.event return false # Successfully found both @event and @proposal when :invalid_proposal flash[:failure] = "Sorry, that presentation proposal doesn't exist or has been deleted." return redirect_to(action: :index) when :invalid_event flash[:failure] = "Sorry, no event was associated with proposal ##{@proposal.id}" return redirect_to(action: :index) end end def assert_user_complete_profile if user_profiles? && logged_in? && !current_user.admin? && !current_user.complete_profile? current_user.complete_profile = true if current_user.valid? current_user.save else flash[:notice] = "Please complete your profile before creating a proposal." store_location return redirect_to(edit_user_path(current_user, require_complete_profile: true)) end end end def assign_proposals_breadcrumb add_breadcrumb "Proposals", proposals_path end def manage_speakers_on_submit if params[:speaker_ids].present? speakers = params[:speaker_ids].map(&:first) unless speakers.blank? speakers.each do |speaker| @proposal.add_user(speaker) end end end end # Return the name of a transition (e.g., "accept") from a Proposal's params. def transition_from_params return params[:proposal].present? ? params[:proposal][:transition] : nil end # Return a sanitized Regexp for matching a speaker by name from the +query+ string. def get_speaker_matcher(query) string = query.gsub(/[[:punct:]]/, ' ').gsub(/\s{2,}/, ' ').strip return Regexp.new(Regexp.escape(string), Regexp::IGNORECASE) end # Return an array of speakers (User records) matching the +query+ string. def get_speaker_matches(query) if query.blank? || ! query.match(/\w+/) return [] else matcher = get_speaker_matcher(query) return(User.complete_profiles.select{|u| u.fullname.try(:match, matcher)} - @proposal.users) end end # Base method used for #show and #session_show def base_show if selector? && @event.accept_selector_votes? @selector_vote = @proposal.selector_votes.find_or_initialize_by(user_id: current_user.id) end unless defined?(@redirector) add_breadcrumb @event.title, event_proposals_path(@event) add_breadcrumb @proposal.title, proposal_path(@proposal) @profile = @proposal.profile @comment = @proposal.comments.new(email: current_email) @display_comment_form = \ # Admin can always leave comments admin? || ( # Display comment form if the event is accepting proposals accepting_proposals? || # or if the settings provide a toggle and the event is accepting comments (event_proposal_comments_after_deadline? && @event.accept_proposal_comments_after_deadline?) ) @focus_comment = false end respond_to do |format| format.html { if defined?(@redirector) @redirector.call else render template: "open_conference_ware/proposals/show" end } format.xml { render xml: @proposal } format.json { render json: @proposal } end end end end