SysMO-DB/seek

View on GitHub
app/controllers/avatars_controller.rb

Summary

Maintainability
B
4 hrs
Test Coverage
class AvatarsController < ApplicationController

  skip_before_filter :project_membership_required

  before_filter :login_required, :except => [ :show ]
  before_filter :check_owner_specified
  before_filter :find_avatars, :only => [ :index ]
  before_filter :find_avatar_auth, :only => [ :show, :select, :edit, :update, :destroy ]
  
  cache_sweeper :avatars_sweeper,:only=>[:destroy,:select,:create]
  
  protect_from_forgery :except => [ :new ]

  include Seek::BreadCrumbs
  
  # GET /people/1/avatars/new
  # GET /people/new
  def new
    store_unsaved_person_proj_inst_data_to_session
    
    @avatar = Avatar.new
  end
  
  # POST /people/1/avatars
  # POST /people
  def create
    unless params[:avatar].blank? || params[:avatar][:image_file].blank?
      file_specified = true
      
      # the creation of the new Avatar instance needs to have only one parameter - therefore, the rest should be set separately
      @avatar = Avatar.new(params[:avatar])
      @avatar.owner_type = params[:owner_type]
      @avatar.owner_id = params[:owner_id]
      @avatar.original_filename = (params[:avatar][:image_file]).original_filename
    else
      file_specified = false
    end
    
    respond_to do |format|
      if file_specified && @avatar.save        
        
        # the last thing to check - if no avatar was selected for owner before (i.e. owner.avatar_id was NULL),
        # make the new avatar selected
        if @avatar.owner.avatar_id.nil?
          @avatar.owner.update_attribute(:avatar_id, @avatar.id)
        end
        
        flash[:notice] = 'Avatar was successfully uploaded.'
        
        # updated to take account of possibly various locations from where this method can be called,
        # so multiple redirect options are possible -> now return link is passed as a parameter
        format.html { redirect_to(params[:return_to] + "?use_unsaved_session_data=true") }
        
      else
        # "create" action was already called once; render it again
        @avatar = Avatar.new
        
        unless file_specified
          flash.now[:error] = "You haven't specified the filename. Please choose the image file to upload."
        else
          flash.now[:error] = "The image format is unreadable. Please try again or select a different image."
        end
        
        format.html { render :action => "new" }
      end
    end
  end
  
  # GET /users/1/avatars/1
  # GET /avatars/1 -> denied by before_filter
  def show
    params[:size] ||= "200x200"
    if params[:size] == "large"
      size = LARGE_SIZE
    else  
      size = params[:size]
    end

    @avatar.resize_image(size)
    
    respond_to do |format|
      format.html do
        send_file(@avatar.full_cache_path(size), :type => 'image/jpg', :disposition => 'inline')
      end
      format.xml do        
        @cache_file=@avatar.full_cache_path(size)
        @type='image/jpg'
      end
    end
    
  end

  #required when first running after switching avatars from .jpg format rather than .png (for lower bandwidth and page load time)
  def convert_jpg_to_png
    png_path = @avatar.file_path
    jpg_path = @avatar.file_path.gsub(/\.png$/, '.jpg')
    image = Magick::Image.read(jpg_path).first
    image.format = "PNG"
    image.write(png_path)
    Rails.logger.info("Converted #{jpg_path} to #{png_path}")
  end
  
  # GET /users/1/avatars
  # GET /avatars -> denied by before_filter
  def index
    respond_to do |format|
      format.html # index.rhtml
    end
  end
  
  
  # DELETE /users/1/avatars/1
  # DELETE /avatars/1 -> denied by before_filter
  def destroy
    owner = @avatar.owner
    
    @avatar.destroy
    
    respond_to do |format|
      format.html { redirect_to eval("#{owner.class.name.downcase}_avatars_url(#{owner.id})") }
    end
  end
  
  
  # GET /users/1/avatars/1/select
  # GET /avatars/1/select -> denied by before_filter
  def select
    if @avatar.select!
      ## IN CONTRAST TO myExperiment SysMO WILL NOT STORE PICTURE SELECTIONS (AT LEAST FOR NOW)
      # create and save picture selection record
      # PictureSelection.create(:user => current_user, :picture => @picture)
      ## END
      
      @avatar.save #forces a update callback, which invokes the sweeper
      respond_to do |format|
        flash[:notice] = 'Profile avatar was successfully updated.'
        format.html { redirect_to eval("#{@avatar_owner_instance.class.name.downcase}_avatars_url(#{@avatar_owner_instance.id})") }
      end
    else
      respond_to do |format|
        flash[:error] = "Avatar was already selected."
        format.html { redirect_to url_for(@avatar_owner_instance) }
      end
    end
  end
  
  protected
  
  def determine_avatar_owner
    avatar_for = nil
    id = nil
    
    if params[:person_id]
      avatar_for = "Person"
      id = params[:person_id]
    elsif params[:project_id]
      avatar_for = "Project"
      id = params[:project_id]
    elsif params[:institution_id]
      avatar_for = "Institution"
      id = params[:institution_id]
    elsif params[:programme_id]
      avatar_for = "Programme"
      id = params[:programme_id]
    end
    
    return [avatar_for, id]
  end
  
  
  private
  
  def check_owner_specified
    @avatar_for, @avatar_for_id = determine_avatar_owner
    
    # check that nested route is used, not a direct link
    if (@avatar_for.nil? || @avatar_for_id.nil?)
      flash[:error] = "Avatars are only available for people, #{t('project').pluralize.downcase} and institutions."
      redirect_to(root_path)
      return false
    end
    
    begin
      # this will find person/project/institution which "owns" in the URL (if it is correct)
      @avatar_owner_instance = @avatar_for.constantize.find(@avatar_for_id)
    rescue ActiveRecord::RecordNotFound
      flash[:error] = "Could not find the #{@avatar_for.downcase} for this avatar."
      redirect_to(root_path)
      return false
    end
  end
  
  
  def find_avatar_auth
    # can show avatars to anyone (given that avatar ID belongs to the avatar owner in the URL);
    # for all other actions some validation is required
    unless self.action_name.to_s == "show"
      if @avatar_for.downcase == "person"
        # for people, users can only edit/select/destroy own avatars - and "person" for current user should exist anyway;
        # (admins can access other people avatars too, provided that the person which is about to be changed is not admin themself)
        unless @avatar_for_id.to_i == current_user.person.id.to_i || @avatar_owner_instance.can_be_edited_by?(current_user)
          flash[:error] = "You can only view and manage your own avatars, but not ones of other users."
          redirect_to(person_path(current_user.person))
          return false
        end
      else
        # project / institution was found - now check if current user has permissions to edit/select/destroy avatars:
        # only selected members AND admins can do so
        unless @avatar_owner_instance.can_be_edited_by?(current_user)
          flash[:error] = "You can only view and, possibly, manage avatars of #{pluralize @avatar_for.downcase}, where you are a member of."
          redirect_to url_for(@avatar_owner_instance)
          return false
        end
      end
    end
    
    # current user is authorised to perform the desired action with avatars of the object in URL;
    # check if the avatar ID in the URL belongs to the specified object
    begin
      @avatar = Avatar.where(:owner_type => @avatar_for, :owner_id => @avatar_for_id).find( params[:id])
    rescue ActiveRecord::RecordNotFound
      flash[:error] = "Avatar not found or belongs to a different #{@avatar_for.downcase}."
      redirect_to(root_path)
      return false
    end
    
  end
  
  
  def find_avatars
    # all avatars for current object are only shown to the owner of the object OR to any admin (if the object is not an admin themself);
    # also, show avatars to all members of a project/institution
    if User.admin_logged_in? || (@avatar_owner_instance.class.name == "Person" && @avatar_for_id.to_i == current_user.person.id.to_i) ||
     (["Project", "Institution"].include?(@avatar_for) && @avatar_owner_instance.can_be_edited_by?(current_user))
      @avatars = Avatar.where(:owner_type => @avatar_for, :owner_id => @avatar_for_id)
    else
      flash[:error] = "You can only change avatars of an item you have the permission to edit."
      redirect_to(person_path(current_user.person))
      return false
    end
  end
  
  # this helper will store to session full form data of edited Person / Project / Institution
  # to allow users to go to "upload new avatar" screen without loosing any new data
  # that wasn't yet saved
  def store_unsaved_person_proj_inst_data_to_session
    data_hash = {}
    
    return if params["#{@avatar_for.downcase}".to_sym].nil?
    
    # all types will have main part of information in the generic form
    data_hash["#{@avatar_for.downcase}".to_sym] = params["#{@avatar_for.downcase}".to_sym]    
    data_hash["#{@avatar_for.downcase}".to_sym][:avatar_id] = nil if data_hash["#{@avatar_for.downcase}".to_sym][:avatar_id].to_i == 0
    
    # collect any additional type-specific data from params
    case @avatar_for
      when "Person"
      data_hash[:description] = params[:description]
      data_hash[:tool] = params[:tool]
      data_hash[:expertise] = params[:expertise]      
      
      when "Project"
      data_hash[:organism] = params[:organism]
      
      when "Institution"
      # no specific data to store for institutions so far
      
    end
    
    # store all collected data to session
    session["unsaved_#{@avatar_for}_#{@avatar_for_id}".to_sym] = data_hash
  end
  
end