# encoding: utf-8
#
# = Main Controller
#
# == Actions
# ==== RssLog's
# list_rss_logs::
# index_rss_log::
# show_rss_log::
# next_rss_log::
# prev_rss_log::
# rss::
#
# ==== Observation's
# show_observation::
# next_observation::
# prev_observation::
# create_observation::
# edit_observation::
# destroy_observation::
# create_naming::
# edit_naming::
# destroy_naming::
# cast_vote::
# show_votes::
#
# ==== Notification's
# show_notifications::
# list_notifications::
#
# ==== Observation Index
# index_observation::
# list_observations::
# observations_by_name::
# observations_by_user::
# observations_at_location::
# observations_at_where::
# observation_search::
# advanced_search::
# show_selected_observations:: (helper)
#
# ==== Searches
# pattern_search::
# advanced_search_form::
# refine_search::
#
# ==== Textile Object Links
# lookup_comment::
# lookup_image::
# lookup_location::
# lookup_name::
# lookup_observation::
# lookup_project::
# lookup_species_list::
# lookup_user::
# lookup_general:: (helper)
#
# ==== Description Authors
# review_authors:: Let authors/reviewers add/remove authors from descriptions.
# author_request:: Let non-authors request authorship credit on descriptions.
#
# ==== Users
# change_user_bonuses::
# index_user::
# users_by_name::
# users_by_contribution::
# show_user::
# show_site_stats::
#
# ==== Email Questions
# ask_webmaster_question::
# email_features::
# send_feature_email::
# ask_user_question::
# ask_observation_question::
# commercial_inquiry::
# email_question:: (helper)
#
# ==== Site Info
# intro::
# how_to_help::
# how_to_use::
# news::
# textile_sandbox::
# translators_note::
#
# ==== Global Callbacks
# no_ajax::
# no_browser::
# no_javascript::
# no_session::
# turn_javascript_on::
# turn_javascript_off::
#
# ==== Themes
# color_themes::
# Agaricus::
# Amanita::
# Cantharellus::
# Hygrocybe::
#
# ==== Test Views
# throw_error::
# throw_mobile_error::
#
# ==== Admin Tools
# recalc::
# refresh_vote_cache::
# clear_session::
# w3c_tests::
#
################################################################################
class ObserverController < ApplicationController
require 'find'
require 'set'
require_dependency 'refine_search'
include RefineSearch
before_filter :login_required, :except => CSS + [
:advanced_search,
:advanced_search_form,
:ask_webmaster_question,
:color_themes,
:how_to_help,
:how_to_use,
:index,
:index_observation,
:index_rss_log,
:index_user,
:intro,
:list_observations,
:list_rss_logs,
:lookup_comment,
:lookup_image,
:lookup_location,
:lookup_name,
:lookup_observation,
:lookup_project,
:lookup_species_list,
:lookup_user,
:news,
:next_observation,
:no_ajax,
:no_browser,
:no_javascript,
:no_session,
:observation_search,
:observations_by_name,
:observations_by_user,
:observations_at_where,
:observations_at_location,
:pattern_search,
:prev_observation,
:refine_search,
:rss,
:show_obs,
:show_observation,
:show_rss_log,
:show_site_stats,
:show_user,
:show_votes,
:test,
:textile,
:textile_sandbox,
:throw_error,
:throw_mobile_error,
:translators_note,
:turn_javascript_nil,
:turn_javascript_off,
:turn_javascript_on,
:user_search,
:users_by_contribution,
:w3c_tests,
]
before_filter :disable_link_prefetching, :except => [
:create_naming,
:create_observation,
:edit_naming,
:edit_observation,
:show_obs,
:show_observation,
:show_user,
:show_votes,
]
##############################################################################
#
# :section: General Stuff
#
##############################################################################
def test
flash_notice "Testing warning message."
end
# Default page. Just displays latest happenings. The actual action is
# buried way down toward the end of this file.
def index # :nologin:
list_rss_logs
end
# Provided just as a way to verify the before_filter.
# This page should always require the user to be logged in.
def login # :norobots:
list_rss_logs
end
# Another test method. Repurpose as needed.
def throw_error # :nologin: :norobots:
raise "Something bad happened."
end
# Used for initial investigation of specialized mobile support
def throw_mobile_error # :nologin: :norobots:
if request.env["HTTP_USER_AGENT"].index("BlackBerry")
raise "This is a BlackBerry!"
else
raise request.env["HTTP_USER_AGENT"].to_s
end
end
def wrapup_2011
end
# Intro to site.
def intro # :nologin:
end
# Recent features.
def news # :nologin:
end
# Help page.
def how_to_use # :nologin:
@min_pos_vote = Vote.agreement(Vote.min_pos_vote).l
@min_neg_vote = Vote.agreement(Vote.min_neg_vote).l
@maximum_vote = Vote.agreement(Vote.maximum_vote).l
end
# A few ways in which users can help.
def how_to_help # :nologin:
end
# Info on color themes.
def color_themes # :nologin:
end
# Explanation of why not having AJAX is bad.
def no_ajax # :nologin: :norobots:
end
# Explanation of why it's important that we recognize the user's browser.
def no_browser # :nologin: :norobots:
end
# Explanation of why having javascript disabled is bad.
def no_javascript # :nologin: :norobots:
end
# Explanation of why having cookies disabled is bad.
def no_session # :nologin: :norobots:
end
# Simple form letting us test our implementation of Textile.
def textile_sandbox # :nologin:
if request.method != :post
@code = nil
else
@code = params[:code]
@submit = params[:commit]
end
render(:action => :textile_sandbox)
end
# I keep forgetting the stupid "_sandbox" thing.
alias_method :textile, :textile_sandbox # :nologin:
# Force javascript on.
def turn_javascript_on # :nologin: :norobots:
session[:js_override] = :on
flash_notice(:turn_javascript_on_body.t)
redirect_to(:back)
rescue ActionController::RedirectBackError
redirect_to('/')
end
# Force javascript off.
def turn_javascript_off # :nologin: :norobots:
session[:js_override] = :off
flash_notice(:turn_javascript_off_body.t)
redirect_to(:back)
rescue ActionController::RedirectBackError
redirect_to('/')
end
# Enable auto-detection.
def turn_javascript_nil # :nologin: :norobots:
session[:js_override] = nil
flash_notice(:turn_javascript_nil_body.t)
redirect_to(:back)
rescue ActionController::RedirectBackError
redirect_to('/')
end
# Simple list of all the files in public/html that are linked to the W3C
# validator to make testing easy.
def w3c_tests # :nologin:
render(:layout => false)
end
# Allow translator to enter a special note linked to from the lower left.
def translators_note # :nologin:
end
##############################################################################
#
# :section: Searches and Indexes
#
##############################################################################
def lookup_comment; lookup_general(Comment); end # :nologin
def lookup_image; lookup_general(Image); end # :nologin
def lookup_location; lookup_general(Location); end # :nologin
def lookup_name; lookup_general(Name); end # :nologin
def lookup_observation; lookup_general(Observation); end # :nologin
def lookup_project; lookup_general(Project); end # :nologin
def lookup_species_list; lookup_general(SpeciesList); end # :nologin
def lookup_user; lookup_general(User); end # :nologin
# Alternative to controller/show_object/id. These were included for the
# benefit of the textile wrapper: We don't want to be looking up all these
# names and objects every time we display comments, etc. Instead we make
# _object_ link to these lookup_object methods, and defer lookup until the
# user actually clicks on one. These redirect to the appropriate
# controller/action after looking up the object.
def lookup_general(model)
objs = []
id = params[:id].to_s.gsub('_',' ').strip_squeeze
begin
if id.match(/^\d+$/)
objs = [model.find(id)]
else
case model.to_s
when 'Location'
pattern1 = "%#{id}%"
pattern2 = "%#{Location.reverse_name(id)}%"
objs = Location.find(:all, :limit => 100, :conditions =>
[ 'name LIKE ? OR name LIKE ?', pattern1, pattern2 ])
when 'Name'
if parse = Name.parse_name(id)
objs = Name.find_all_by_search_name(parse[3])
objs = Name.find_all_by_text_name(parse[0]) if objs.empty?
end
when 'Project'
pattern = "%#{id}%"
objs = Project.find(:all, :limit => 100, :conditions =>
[ 'title LIKE ?', pattern ])
when 'SpeciesList'
pattern = "%#{id}%"
objs = SpeciesList.find(:all, :limit => 100, :conditions =>
[ 'title LIKE ?', pattern ])
when 'User'
objs = User.find_all_by_login(id)
objs = User.find_all_by_name(id) if objs.empty?
end
end
rescue
end
if objs.empty?
type = model.type_tag
flash_error(:runtime_object_no_match.t(:match => id, :type => type))
goto_index(model)
elsif objs.length == 1
obj = objs.first
redirect_to(:controller => obj.show_controller, :action => obj.show_action, :id => obj.id)
else
obj = objs.first
query = Query.lookup(model, :in_set, :ids => objs)
flash_warning(:runtime_object_multiple_matches.t(:match => id, :type => type))
redirect_to(:controller => obj.show_controller, :action => obj.index_action,
:params => query_params(query))
end
end
# This is the action the search bar commits to. It just redirects to one of
# several "foreign" search actions:
# comment/image_search
# image/image_search
# location/location_search
# name/name_search
# observer/observation_search
# observer/user_search
# project/project_search
# species_list/species_list_search
def pattern_search # :nologin: :norobots:
pattern = params[:search][:pattern].to_s.strip_squeeze rescue nil
type = params[:search][:type].to_sym rescue nil
# Save it so that we can keep it in the search bar in subsequent pages.
session[:pattern] = pattern
session[:search_type] = type
case type
when :observation, :user, :google
ctrlr = 'observer'
when :comment, :image, :location, :name, :project, :species_list
ctrlr = type
else
flash_error(:runtime_invalid.t(:type => :search, :value => type.inspect))
redirect_back_or_default(:action => 'list_rss_logs')
end
# If pattern is blank, this would devolve into a very expensive index.
if pattern.blank?
type = 'rss_log' if type == :google
redirect_to(:controller => ctrlr, :action => "list_#{type}s")
elsif type == :google
pat = URI.escape("site:#{DOMAIN} #{pattern}")
redirect_to("http://google.com?q=#{pat}")
else
redirect_to(:controller => ctrlr, :action => "#{type}_search",
:pattern => pattern)
end
end
# Advanced search form. When it posts it just redirects to one of several
# "foreign" search actions:
# image/advanced_search
# name/advanced_search
# observer/advanced_search
def advanced_search_form # :nologin: :norobots:
if request.method != :post
@location_primer = Location.primer
@name_primer = Name.primer
@user_primer = User.primer
else
model = params[:search][:type].to_s.camelize.constantize
# Pass along all given search fields (remove angle-bracketed user name,
# though, since it was only included by the auto-completer as a hint).
search = {}
if !(x = params[:search][:name].to_s).blank?
search[:name] = x
end
if !(x = params[:search][:location].to_s).blank?
search[:location] = x
end
if !(x = params[:search][:user].to_s).blank?
search[:user] = x.sub(/ <.*/, '')
end
if !(x = params[:search][:content].to_s).blank?
search[:content] = x
end
# Create query (this just validates the parameters).
query = create_query(model, :advanced_search, search)
# Let the individual controllers execute and render it.
redirect_to(:controller => model.show_controller,
:action => 'advanced_search', :params => query_params(query))
end
end
# Allow users to refine an existing query.
def refine_search # :nologin:
# Create a bogus object with all the parameters used in the form.
@values = Wrapper.new(params[:values] || {})
@first_time = params[:values].blank?
@goto_index = true if params[:commit] == :refine_search_goto_index.l
@errors = []
# Query has expired!
if !(@query = find_query)
flash_error(:runtime_search_has_expired.t)
redirect_back_or_default(:action => 'list_rss_logs')
else
# Need to know about change to basic flavor immediately.
if @first_time || @values.model_flavor.blank?
query2 = @query
else
model2, flavor2 = @values.model_flavor.to_s.split(' ', 2)
query2 = Query.template(model2.camelize, flavor2) rescue @query
end
model2 = query2.model_symbol
flavor2 = query2.flavor
# Get Array of parameters we can play with.
@fields = refine_search_get_fields(query2)
# Modify the query on POST, test it out, and redirect or re-serve form.
if !@first_time &&
(request.method == :post) and !is_robot?
params2 = refine_search_clone_params(query2, @query.params)
@errors = refine_search_change_params(@fields, @values, params2)
if @errors.any?
# Already flashed errors.
# No changes. This may not be apparent due to vagaries of parsing.
# This will change all the form values to be what's currently in the
# query. The user will then know exactly why we say "no changes".
elsif (model2 == @query.model_symbol) and
(flavor2 == @query.flavor) and
(params2 == @query.params)
flash_notice(:runtime_no_changes.t) if !@goto_index
else
begin
# Create and initialize the new query to test it out.
query2 = Query.lookup(model2, flavor2, params2)
query2.initialize_query
query2.save
@query = query2
if !@goto_index
flash_notice(:refine_search_success.t(:num => @query.num_results))
end
rescue => e
flash_error(e)
# flash_error(e.backtrace.join(" "))
end
end
end
end
# Redisplay the index if user presses "Index".
if @goto_index
redirect_to(:controller => @query.model.show_controller,
:action => @query.model.index_action,
:params => query_params(@query))
else
# flash_notice(@query.query)
@flavor_field = refine_search_flavor_field
@values.model_flavor = "#{model2.to_s.underscore} #{flavor2}"
refine_search_initialize_values(@fields, @values, @query)
end
end
# Displays matrix of selected Observation's (based on current Query).
def index_observation # :nologin: :norobots:
query = find_or_create_query(:Observation, :by => params[:by])
show_selected_observations(query, :id => params[:id],
:always_index => true)
end
# Displays matrix of all Observation's, sorted by date.
def list_observations # :nologin:
query = create_query(:Observation, :all, :by => :date)
show_selected_observations(query)
end
# Displays matrix of all Observation's, alphabetically.
def observations_by_name # :nologin: :norobots:
query = create_query(:Observation, :all, :by => :name)
show_selected_observations(query)
end
# Displays matrix of User's Observation's, by date.
def observations_by_user # :nologin: :norobots:
user = params[:id] ? find_or_goto_index(User, params[:id]) : @user
if !user
flash_error(:runtime_missing.t(:field => 'id'))
redirect_to(:action => 'list_rss_logs')
else
query = create_query(:Observation, :by_user, :user => user)
show_selected_observations(query)
end
end
# Displays matrix of Observation's at a Location, by date.
def observations_at_location # :nologin: :norobots:
if location = find_or_goto_index(Location, params[:id])
query = create_query(:Observation, :at_location, :location => location)
show_selected_observations(query)
end
end
alias show_location_observations observations_at_location
# Display matrix of Observation's whose 'where' matches a string.
def observations_at_where # :nologin: :norobots:
where = params[:where].to_s
params[:location] = where
query = create_query(:Observation, :at_where,
:user_where => where, :location => Location.user_name(@user, where))
show_selected_observations(query, {:always_index => 1})
end
# Display matrix of Observation's whose notes, etc. match a string pattern.
def observation_search # :nologin: :norobots:
pattern = params[:pattern].to_s
if pattern.match(/^\d+$/) and
(observation = Observation.safe_find(pattern))
redirect_to(:action => 'show_observation', :id => observation.id)
else
query = create_query(:Observation, :pattern_search, :pattern => pattern)
show_selected_observations(query)
end
end
# Displays matrix of advanced search results.
def advanced_search # :nologin: :norobots:
begin
query = find_query(:Observation)
show_selected_observations(query)
rescue => err
flash_error(err.to_s) if !err.blank?
redirect_to(:controller => 'observer', :action => 'advanced_search_form')
end
end
# Show selected search results as a matrix with 'list_observations' template.
def show_selected_observations(query, args={})
store_query_in_session(query)
@links ||= []
args = {
:action => 'list_observations',
:matrix => true,
:include => [:name, :location, :user, :rss_log],
}.merge(args)
# Add some extra links to the index user is sent to if they click on an
# undefined location.
if query.flavor == :at_where
@links += [
[ :list_observations_location_define.l, { :controller => 'location',
:action => 'create_location', :where => query.params[:user_where] } ],
[ :list_observations_location_merge.l, { :controller => 'location',
:action => 'list_merge_options', :where => query.params[:user_where] } ],
[ :list_observations_location_all.l, { :controller => 'location',
:action => 'list_locations' } ],
]
end
# Add some alternate sorting criteria.
args[:sorting_links] = [
['name', :sort_by_name.t],
['date', :sort_by_date.t],
['user', :sort_by_user.t],
['created', :sort_by_posted.t],
[(query.flavor == :by_rss_log ? 'rss_log' : 'modified'),
:sort_by_modified.t],
['confidence', :sort_by_confidence.t],
['thumbnail_quality', :sort_by_thumbnail_quality.t],
['num_views', :sort_by_num_views.t],
]
# Add "show map" link if this query can be coerced into a location query.
if query.is_coercable?(:Location)
@links << [:show_object.t(:type => :map), {
:controller => 'location',
:action => 'map_locations',
:params => query_params(query),
}]
@links << [:show_objects.t(:type => :location), {
:controller => 'location',
:action => 'index_location',
:params => query_params(query),
}]
end
# Add "show names" link if this query can be coerced into a name query.
if query.is_coercable?(:Name)
@links << [:show_objects.t(:type => :name), {
:controller => 'name',
:action => 'index_name',
:params => query_params(query),
}]
end
# Add "show images" link if this query can be coerced into an image query.
if query.is_coercable?(:Image)
@links << [:show_objects.t(:type => :image), {
:controller => 'image',
:action => 'index_image',
:params => query_params(query),
}]
end
# Paginate by letter if sorting by user.
if (query.params[:by] == 'user') or
(query.params[:by] == 'reverse_user')
args[:letters] = 'users.login'
# Paginate by letter if names are included in query.
elsif query.uses_table?(:names)
args[:letters] = 'names.text_name'
end
show_index_of_objects(query, args)
end
# Map results of a search or index.
def map_observations # :nologin: :norobots:
@query = find_or_create_query(:Observation)
@title = :map_locations_title.t(:locations => @query.title)
@observations = @query.results.select {|o| o.lat or o.location}
end
##############################################################################
#
# :section: Show Observation
#
##############################################################################
# Display observation and related namings, comments, votes, images, etc.
# This should be redirected_to, not rendered, due to large number of
# @variables that need to be set up for the view. Lots of views are used:
# show_observation
# _show_observation
# _show_images
# _show_namings
# _show_comments
# _show_footer
# Linked from countless views as a fall-back.
# Inputs: params[:id]
# Outputs:
# @observation
# @confidence/agreement_menu (used to create vote menus)
# @votes (user's vote for each naming.id)
def show_observation # :nologin: :prefetch:
pass_query_params
store_location
# Make it really easy for users to elect to go public with their votes.
if params[:go_public] == '1'
@user.votes_anonymous = :no
@user.save
flash_notice(:show_votes_gone_public.t)
elsif params[:go_private] == '1'
@user.votes_anonymous = :yes
@user.save
flash_notice(:show_votes_gone_private.t)
end
# Make it easy for users to change thumbnail size.
if !params[:set_thumbnail_size].blank?
set_default_thumbnail_size(params[:set_thumbnail_size])
end
if @observation = find_or_goto_index(Observation, params[:id], :include => [
{:comments => :user},
:images,
:location,
:name,
{:namings => [:name, :user, {:votes => :user}]},
{:species_lists => :location},
:user,
])
update_view_stats(@observation)
# Decide if the current query can be used to create a map.
query = find_query(:Observation)
@mappable = query && query.is_coercable?(:Location)
if @user
# This happens when user clicks on "Update Votes".
if request.method == :post
if params[:vote]
flashed = false
for naming in @observation.namings
if (value = params[:vote][naming.id.to_s][:value].to_i rescue nil) and
@observation.change_vote(naming, value) and
!flashed
flash_notice(:runtime_show_observation_success.t)
flashed = true
end
end
end
end
# Provide a list of user's votes to view.
@votes = {}
for naming in @observation.namings
vote = naming.votes.select {|x| x.user_id == @user.id}.first
vote ||= Vote.new(:value => 0)
@votes[naming.id] = vote
end
@confidence_menu = translate_menu(Vote.confidence_menu)
@agreement_menu = translate_menu(Vote.agreement_menu)
end
end
end
def show_obs
redirect_to(:action => 'show_observation', :id => params[:id])
end
# Go to next observation: redirects to show_observation.
def next_observation # :nologin: :norobots:
redirect_to_next_object(:next, Observation, params[:id])
end
# Go to previous observation: redirects to show_observation.
def prev_observation # :nologin: :norobots:
redirect_to_next_object(:prev, Observation, params[:id])
end
##############################################################################
#
# :section: Create and Edit Observations
#
##############################################################################
# Form to create a new observation, naming, vote, and images.
# Linked from: left panel
#
# Inputs:
# params[:observation][...] observation args
# params[:name][:name] name
# params[:approved_name] old name
# params[:approved_where] old place name
# params[:chosen_name][:name_id] name radio boxes
# params[:vote][...] vote args
# params[:reason][n][...] naming_reason args
# params[:image][n][...] image args
# params[:good_images] images already downloaded
# params[:was_js_on] was form javascripty? ('yes' = true)
#
# Outputs:
# @observation, @naming, @vote empty objects
# @what, @names, @valid_names name validation
# @confidence_menu used for vote option menu
# @reason array of naming_reasons
# @images array of images
# @licenses used for image license menu
# @new_image blank image object
# @good_images list of images already downloaded
#
def create_observation # :prefetch: :norobots:
# These are needed to create pulldown menus in form.
@licenses = License.current_names_and_ids(@user.license)
@new_image = init_image(Time.now)
@confidence_menu = translate_menu(Vote.confidence_menu)
# Clear search list. [Huh? -JPH 20120513]
clear_query_in_session
# Create empty instances first time through.
if request.method != :post
create_observation_get
else
create_observation_post(params)
end
end
def create_observation_post(params)
rough_cut(params) # creates @observation, @naming, @vote, @good/bad_images,
# flashes errors associated with image updates
success = true
success = false if !validate_name(params) # sets @name, @names, @valid_names, @what, etc.
success = false if !validate_place_name(params) # sets @dubious_xxx, @place_name
success = false if !validate_observation(@observation) # flashes errors
success = false if @name and !validate_naming(@naming) # flashes errors
success = false if @name and !validate_vote(@vote) # flashes errors
success = false if @bad_images != []
success = false if success and !save_observation(@observation) # should always succeed
# Once observation is saved we can save everything else.
if success
save_everything_else(params[:reason]) # should always succeed
flash_notice(:runtime_observation_success.t(:id => @observation.id))
@observation.log(:log_observation_created)
redirect_to_next_page # just redirects
# If anything failed reload the form.
else
reload_the_form(params[:reason]) # sets @reason, @images, @new_image, @location_primer, etc.
end
end
def rough_cut(params)
# Create everything roughly first.
@observation = create_observation_object(params[:observation])
@naming = create_naming_object(params[:naming], @observation)
@vote = create_vote_object(params[:vote], @naming)
@good_images = update_good_images(params[:good_images])
@bad_images = create_image_objects(params[:image], @observation, @good_images)
end
def validate_name(params)
given_name = params[:name][:name].to_s rescue ''
chosen_name = params[:chosen_name][:name_id].to_s rescue ''
(success, @what, @name, @names, @valid_names) =
resolve_name(given_name, params[:approved_name], chosen_name)
@naming.name = @name if @name
return success
end
def validate_place_name(params)
success = true
@place_name = @observation.place_name
@dubious_where_reasons = []
if @place_name != params[:approved_where] and @observation.location.nil?
db_name = Location.user_name(@user, @place_name)
@dubious_where_reasons = Location.dubious_name?(db_name, true)
success = false if @dubious_where_reasons != []
end
return success
end
def save_everything_else(reason)
if @name
create_naming_reasons(@naming, reason)
save_naming(@naming)
@observation.reload
@observation.change_vote(@naming, @vote.value)
end
attach_good_images(@observation, @good_images)
update_projects(@observation, params[:project])
update_species_lists(@observation, params[:list])
end
def redirect_to_next_page
if @observation.location.nil?
redirect_to(:controller => 'location', :action => 'create_location',
:where => @observation.place_name, :set_observation => @observation.id)
elsif has_unshown_notifications?(@user, :naming)
redirect_to(:action => 'show_notifications', :id => @observation.id)
else
redirect_to(:action => 'show_observation', :id => @observation.id)
end
end
def reload_the_form(reason)
@reason = init_naming_reasons(@naming, reason)
@images = @bad_images
@new_image.when = @observation.when
@location_primer = Location.primer
@name_primer = Name.primer
init_project_vars_for_reload(@observation)
init_list_vars_for_reload(@observation)
end
def create_observation_get
@observation = Observation.new
@naming = Naming.new
@vote = Vote.new
@what = '' # can't be nil else rails tries to call @name.name
@names = nil
@valid_names = nil
@reason = init_naming_reasons(@naming)
@images = []
@good_images = []
@location_primer = Location.primer
@name_primer = Name.primer
init_project_vars_for_create
init_list_vars_for_create
get_defaults_from_last_observation_created
end
def get_defaults_from_last_observation_created
# Grab defaults for date and location from last observation the user
# edited if it was less than an hour ago.
last_observation = Observation.find_by_user_id(@user.id, :order => 'created DESC')
if last_observation && last_observation.created > 1.hour.ago
@observation.when = last_observation.when
@observation.where = last_observation.where
@observation.location = last_observation.location
@observation.lat = last_observation.lat
@observation.long = last_observation.long
@observation.alt = last_observation.alt
for project in last_observation.projects
@project_checks[project.id] = true
end
for list in last_observation.species_lists
if check_permission(list)
@lists << list unless @lists.include?(list)
@list_checks[list.id] = true
end
end
end
end
# Form to edit an existing observation.
# Linked from: left panel
#
# Inputs:
# params[:id] observation id
# params[:observation][...] observation args
# params[:image][n][...] image args
# params[:log_change][:checked] log change in RSS feed?
#
# Outputs:
# @observation populated object
# @images array of images
# @licenses used for image license menu
# @new_image blank image object
# @good_images list of images already attached
#
def edit_observation # :prefetch: :norobots:
pass_query_params
@observation = Observation.find(params[:id], :include =>
[:name, :images, :location])
@licenses = License.current_names_and_ids(@user.license)
@new_image = init_image(@observation.when)
# Make sure user owns this observation!
if !check_permission!(@observation)
redirect_to(:action => 'show_observation', :id => @observation.id,
:params => query_params)
# Initialize form.
elsif request.method != :post
@images = []
@good_images = @observation.images
init_project_vars_for_edit(@observation)
init_list_vars_for_edit(@observation)
else
# Update observation attributes
@observation.attributes = params[:observation]
# Validate place name.
@place_name = @observation.place_name
@dubious_where_reasons = []
if @place_name != params[:approved_where] and @observation.location.nil?
db_name = Location.user_name(@user, @place_name)
@dubious_where_reasons = Location.dubious_name?(db_name, true)
end
# Now try to upload images.
@good_images = update_good_images(params[:good_images])
@bad_images = create_image_objects(params[:image], @observation, @good_images)
attach_good_images(@observation, @good_images)
# Only save observation if there are changes.
done = false
if @dubious_where_reasons == []
if @observation.changed?
@observation.modified = Time.now
if done = save_observation(@observation)
flash_notice(:runtime_edit_observation_success.t(:id => @observation.id))
touch = (params[:log_change][:checked] == '1' rescue false)
@observation.log(:log_observation_updated, :touch => touch)
end
else
done = true
end
end
# Update project and species_list attachments.
update_projects(@observation, params[:project])
update_species_lists(@observation, params[:list])
# Redirect to show_observation or create_location on success.
if done && @bad_images.empty?
if @observation.location.nil?
redirect_to(:controller => 'location', :action => 'create_location', :where => @observation.place_name,
:set_observation => @observation.id, :params => query_params)
else
redirect_to(:action => 'show_observation', :id => @observation.id, :params => query_params)
end
end
# Reload form if anything failed.
if not done
@images = @bad_images
@new_image.when = @observation.when
init_project_vars_for_reload(@observation)
init_list_vars_for_reload(@observation)
end
end
end
# Callback to destroy an observation (and associated namings, votes, etc.)
# Linked from: show_observation
# Inputs: params[:id] (observation)
# Redirects to list_observations.
def destroy_observation # :norobots:
# All of this just to decide where to redirect after deleting observation.
@observation = Observation.find(params[:id])
next_state = nil
if this_state = find_query(:Observation)
this_state.current = @observation
next_state = this_state.next
end
if !check_permission!(@observation)
flash_error(:runtime_destroy_observation_denied.t(:id => @observation.id))
redirect_to(:action => 'show_observation', :id => @observation.id,
:params => query_params(this_state))
elsif !@observation.destroy
flash_error(:runtime_destroy_observation_failed.t(:id => @observation.id))
redirect_to(:action => 'show_observation', :id => @observation.id,
:params => query_params(this_state))
else
Transaction.delete_observation(:id => @observation)
flash_notice(:runtime_destroy_observation_success.t(:id => params[:id]))
if next_state
redirect_to(:action => 'show_observation', :id => next_state.current_id,
:params => query_params(next_state))
else
redirect_to(:action => 'list_observations')
end
end
end
##############################################################################
#
# :section: Create and Edit Namings
#
##############################################################################
# Form to propose new naming for an observation.
# Linked from: show_observation
#
# Inputs (post):
# params[:id] observation id
# params[:name][:name] name
# params[:approved_name] old name
# params[:chosen_name][:name_id] name radio boxes
# params[:vote][...] vote args
# params[:reason][n][...] naming_reason args
# params[:was_js_on] was form javascripty? ('yes' = true)
#
# Outputs:
# @observation, @naming, @vote empty objects
# @what, @names, @valid_names name validation
# @confidence_menu used for vote option menu
# @reason array of naming_reasons
#
def create_naming # :prefetch: :norobots:
pass_query_params
@observation = Observation.find(params[:id])
@confidence_menu = translate_menu(Vote.confidence_menu)
# Create empty instances first time through.
if request.method != :post
@naming = Naming.new
@vote = Vote.new
@what = '' # can't be nil else rails tries to call @name.name
@names = nil
@valid_names = nil
@reason = init_naming_reasons(@naming)
else
# Create everything roughly first.
@naming = create_naming_object(params[:naming], @observation)
@vote = create_vote_object(params[:vote], @naming)
# Validate name.
given_name = params[:name][:name].to_s rescue ''
chosen_name = params[:chosen_name][:name_id].to_s rescue ''
(success, @what, @name, @names, @valid_names) =
resolve_name(given_name, params[:approved_name], chosen_name)
if !@name
if !given_name.match(/\S/)
@naming.errors.add(:name, :validate_naming_name_missing.t)
flash_object_errors(@naming)
end
success = false
end
if success && @observation.name_been_proposed?(@name)
flash_warning(:runtime_create_naming_already_proposed.t)
success = false
end
# Validate objects.
@naming.name = @name
success = validate_naming(@naming) if success
success = validate_vote(@vote) if success
if success
# Save changes now that everything checks out.
create_naming_reasons(@naming, params[:reason])
save_naming(@naming)
@observation.reload
@observation.change_vote(@naming, @vote.value)
@observation.log(:log_naming_created, :name => @naming.format_name)
# Check for notifications.
if has_unshown_notifications?(@user, :naming)
redirect_to(:action => 'show_notifications', :id => @observation.id,
:params => query_params)
else
redirect_to(:action => 'show_observation', :id => @observation.id,
:params => query_params)
end
# If anything failed reload the form.
else
@reason = init_naming_reasons(@naming, params[:reason])
end
end
end
# Form to edit an existing naming for an observation.
# Linked from: show_observation
#
# Inputs:
# params[:id] naming id
# params[:observation_id] (alternate way to enter)
# params[:name][:name] name
# params[:approved_name] old name
# params[:chosen_name][:name_id] name radio boxes
# params[:vote][...] vote args
# params[:reason][n][...] naming_reason args
# params[:was_js_on] was form javascripty? ('yes' = true)
#
# Outputs:
# @observation, @naming, @vote empty objects
# @what, @names, @valid_names name validation
# @confidence_menu used for vote option menu
# @reason array of naming_reasons
#
def edit_naming # :prefetch: :norobots:
pass_query_params
if !params[:id].blank?
@naming = Naming.find(params[:id])
@observation = @naming.observation
else
@observation = Observation.find(params[:observation_id])
@naming = @observation.consensus_naming
end
@vote = Vote.find(:first, :conditions =>
['naming_id = ? AND user_id = ?', @naming.id, @naming.user_id])
@confidence_menu = translate_menu(Vote.confidence_menu)
# Make sure user owns this naming!
if !check_permission!(@naming)
redirect_to(:action => 'show_observation', :id => @observation.id,
:params => query_params)
# Initialize form.
elsif request.method != :post
@what = @naming.text_name
@names = nil
@valid_names = nil
@reason = init_naming_reasons(@naming)
else
# Validate name.
given_name = params[:name][:name].to_s rescue ''
chosen_name = params[:chosen_name][:name_id].to_s rescue ''
(success, @what, @name, @names, @valid_names) =
resolve_name(given_name, params[:approved_name], chosen_name)
if !@name
if !given_name.match(/\S/)
@naming.errors.add(:name, :validate_naming_name_missing.t)
flash_object_errors(@naming)
end
success = false
end
if success and (@naming.name != @name) and
@observation.name_been_proposed?(@name)
flash_warning(:runtime_edit_naming_someone_else.t)
success = false
end
# Owner is not allowed to change a naming once it's been used by someone
# else. Instead I automatically clone it and make changes to the clone.
# I assume there will be no validation problems since we're cloning
# pre-existing valid objects.
if success and !@naming.editable? and (@name != @naming.name)
@naming = create_naming_object(params[:naming], @observation)
@vote = create_vote_object(params[:vote], @naming)
# Validate objects.
@naming.name = @name
success = validate_naming(@naming) if success
success = validate_vote(@vote) if success
# Save changes now that everything checks out.
if success
create_naming_reasons(@naming, params[:reason])
save_naming(@naming)
@observation.reload
@observation.change_vote(@naming, @vote.value, @naming.user)
@observation.log(:log_naming_created, :name => @naming.format_name)
flash_warning 'Sorry, someone else has given this a positive vote,
so we had to create a new Naming to accomodate your changes.'
end
# Owner is allowed to change the naming so long as no one else has used it.
# They are also allowed to change the reasons even if others have used it.
elsif success
# If user's changed the name, it sorta invalidates any votes that
# others might have cast prior to this.
need_to_calc_consensus = false
need_to_log_change = false
if @name != @naming.name
for vote in @naming.votes
if vote.user_id != @user.id
vote.destroy
end
end
need_to_calc_consensus = true
need_to_log_change = true
end
# Update reasons.
create_naming_reasons(@naming, params[:reason])
# Make changes to naming.
success = update_naming_object(@naming, @name, need_to_log_change)
# Save everything if it all checks out.
if success
# Only change vote if changed value.
if (new_val = params[:vote][:value].to_i rescue nil) and
(!@vote || @vote.value != new_val)
@observation.change_vote(@naming, new_val)
need_to_calc_consensus = false
end
if need_to_calc_consensus
@observation.reload
@observation.calc_consensus
end
end
end
# Redirect to observation on success, reload form if anything failed.
if success
redirect_to(:action => 'show_observation', :id => @observation.id,
:params => query_params)
else
@reason = init_naming_reasons(@naming, params[:reason])
end
end
end
# Callback to destroy a naming (and associated votes, etc.)
# Linked from: show_observation
# Inputs: params[:id] (observation)
# Redirects back to show_observation.
def destroy_naming # :norobots:
pass_query_params
@naming = Naming.find(params[:id])
@observation = @naming.observation
if !check_permission!(@naming)
flash_error(:runtime_destroy_naming_denied.t(:id => @naming.id))
elsif !@naming.deletable?
flash_warning(:runtime_destroy_naming_someone_else.t)
elsif !@naming.destroy
flash_error(:runtime_destroy_naming_failed.t(:id => @naming.id))
else
Transaction.delete_naming(:id => @naming)
flash_notice(:runtime_destroy_naming_success.t(:id => params[:id]))
end
redirect_to(:action => 'show_observation', :id => @observation.id,
:params => query_params)
end
# I'm tired of tweaking show_observation to call calc_consensus for debugging.
# I'll just leave this stupid action in and have it forward to show_observation.
def recalc # :root: :norobots:
pass_query_params
id = params[:id]
begin
@observation = Observation.find(id)
flash_notice(:observer_recalc_old_name.t(:name => @observation.name.display_name))
text = @observation.calc_consensus(true)
flash_notice text if !text.blank?
flash_notice(:observer_recalc_new_name.t(:name => @observation.name.display_name))
rescue => err
flash_error(:observer_recalc_caught_error.t(:error => err))
end
# render(:text => '', :layout => true)
redirect_to(:action => 'show_observation', :id => id,
:params => query_params)
end
##############################################################################
#
# :section: Vote Stuff
#
##############################################################################
# Create vote if none exists; change vote if exists; delete vote if setting
# value to -1 (owner of naming is not allowed to do this).
# Linked from: (nowhere)
# Inputs: params[]
# Redirects to show_observation.
def cast_vote # :norobots:
pass_query_params
naming = Naming.find(params[:id])
observation = naming.observation
value = params[:value].to_i
observation.change_vote(naming, value)
redirect_to(:action => 'show_observation', :id => observation.id,
:params => query_params)
end
# Show breakdown of votes for a given naming.
# Linked from: show_observation
# Inputs: params[:id] (naming)
# Outputs: @naming
def show_votes # :nologin: :prefetch:
pass_query_params
@naming = find_or_goto_index(Naming, params[:id], :include => [:name, :votes])
end
# Refresh vote cache for all observations in the database.
def refresh_vote_cache # :root: :norobots:
if is_in_admin_mode?
# Naming.refresh_vote_cache
Observation.refresh_vote_cache
flash_notice(:refresh_vote_cache.t)
redirect_to(:action => 'list_rss_logs')
end
end
##############################################################################
#
# :section: Reviewer Utilities
#
##############################################################################
# Form to compose email for the authors/reviewers. Linked from show_