tracks/app/controllers/todos_controller.rb
Reinier Balt 5092b388fe Fasterer: Parallel assignment is slower
Parallel assignment is slower than sequential assignment.
Only got the low hanging fruit. There are some functions that
have multiple return values. Fixing this needs more refactoring.
2015-08-19 15:12:52 +02:00

1373 lines
48 KiB
Ruby

class TodosController < ApplicationController
skip_before_filter :login_required, :only => [:index, :tag]
prepend_before_filter :login_or_feed_token_required, :only => [:index, :tag]
append_before_filter :find_and_activate_ready, :only => [:index, :list_deferred]
protect_from_forgery :except => :check_deferred
def index
@source_view = params['_source_view'] || 'todo'
init_data_for_sidebar unless mobile?
@todos = current_user.todos.includes(Todo::DEFAULT_INCLUDES)
@todos = @todos.limit(sanitize(params[:limit])) if params[:limit]
@not_done_todos = get_not_done_todos
@projects = current_user.projects.includes(:default_context)
@contexts = current_user.contexts
@contexts_to_show = current_user.contexts.active
@projects_to_show = current_user.projects.active
# If you've set no_completed to zero, the completed items box isn't shown
# on the home page
max_completed = current_user.prefs.show_number_completed
@done = current_user.todos.completed.limit(max_completed).includes(Todo::DEFAULT_INCLUDES) unless max_completed == 0
respond_to do |format|
format.html do
@page_title = t('todos.task_list_title')
# Set count badge to number of not-done, not hidden context items
@count = current_user.todos.active.not_hidden.count(:all)
@todos_without_project = @not_done_todos.select{|t|t.project.nil?}
end
format.m do
@page_title = t('todos.mobile_todos_page_title')
@home = true
cookies[:mobile_url]= { :value => request.fullpath, :secure => SITE_CONFIG['secure_cookies']}
determine_down_count
render :action => 'index'.freeze
end
format.text do
# somehow passing Mime::TEXT using content_type to render does not work
headers['Content-Type'.freeze]=Mime::TEXT.to_s
render :content_type => Mime::TEXT
end
format.xml do
@xml_todos = params[:limit_to_active_todos] ? @not_done_todos : @todos
render :xml => @xml_todos.to_xml( *todo_xml_params )
end
format.any(:rss, :atom) do
@feed_title = 'Tracks Actions'.freeze
@feed_description = "Actions for #{current_user.display_name}"
end
format.ics
end
end
def new
@projects = current_user.projects.active
@contexts = current_user.contexts
respond_to do |format|
format.m {
@new_mobile = true
@return_path=cookies[:mobile_url] ? cookies[:mobile_url] : mobile_path
@mobile_from_context = current_user.contexts.find(params[:from_context]) if params[:from_context]
@mobile_from_project = current_user.projects.find(params[:from_project]) if params[:from_project]
if params[:from_project] && !params[:from_context]
# we have a project but not a context -> use the default context
@mobile_from_context = @mobile_from_project.default_context
end
}
end
end
def create
@source_view = params['_source_view'] || 'todo'
@default_context = current_user.contexts.where(:name => params['default_context_name']).first
@default_project = current_user.projects.where(:name => params['default_project_name']).first if params['default_project_name'].present?
@tag_name = params['_tag_name']
is_multiple = params[:todo] && params[:todo][:multiple_todos] && !params[:todo][:multiple_todos].nil?
if is_multiple
create_multiple
else
p = Todos::TodoCreateParamsHelper.new(params, current_user)
p.parse_dates unless mobile?
tag_list = p.tag_list
@todo = current_user.todos.build
@todo.assign_attributes(p.attributes)
p.add_errors(@todo)
if @todo.errors.empty?
@todo.add_predecessor_list(p.predecessor_list)
@saved = @todo.save
@todo.tag_with(tag_list) if @saved && tag_list.present?
@todo.update_state_from_project if @saved
@todo.block! if @todo.should_be_blocked?
else
@saved = false
end
@todo_was_created_deferred = @todo.deferred?
@todo_was_created_blocked = @todo.pending?
@not_done_todos = [@todo] if p.new_project_created || p.new_context_created
@new_project_created = p.new_project_created
@new_context_created = p.new_context_created
respond_to do |format|
format.html do
redirect_to :action => "index"
end
format.m do
@return_path=cookies[:mobile_url] ? cookies[:mobile_url] : mobile_path
if @saved
onsite_redirect_to @return_path
else
@projects = current_user.projects
@contexts = current_user.contexts
render :action => "new"
end
end
format.js do
if @saved
determine_down_count
@contexts = current_user.contexts
@projects = current_user.projects
@context = @todo.context
@project = @todo.project
@initial_context_name = params['default_context_name']
@initial_project_name = params['default_project_name']
@initial_tags = params['initial_tag_list']
@status_message = t('todos.added_new_next_action')
@status_message += ' ' + t('todos.to_tickler') if @todo.deferred?
@status_message += ' ' + t('todos.in_pending_state') if @todo.pending?
@status_message += ' ' + t('todos.in_hidden_state') if @todo.hidden?
@status_message = t('todos.added_new_project') + ' / ' + @status_message if @new_project_created
@status_message = t('todos.added_new_context') + ' / ' + @status_message if @new_context_created
end
render :action => 'create'
end
format.xml do
if @saved
head :created, :location => todo_url(@todo)
else
render_failure @todo.errors.to_xml.html_safe, 409
end
end
end
end
end
def create_multiple
p = Todos::TodoCreateParamsHelper.new(params, current_user)
tag_list = p.tag_list
@not_done_todos = []
@build_todos = []
@todos = []
errors = []
@predecessor = nil
validates = true
# first build all todos and check if they would validate on save
params[:todo][:multiple_todos].split("\n").map do |line|
if line.present? #ignore blank lines
@todo = current_user.todos.build({:description => line, :context_id => p.context_id, :project_id => p.project_id})
validates &&= @todo.valid?
@build_todos << @todo
end
end
# if all todos validate, then save them and add predecessors and tags
if validates
@build_todos.each do |todo|
@saved = todo.save
validates &&= @saved
if @predecessor && @saved && p.sequential?
todo.add_predecessor(@predecessor)
todo.block!
end
todo.tag_with(tag_list) if @saved && tag_list.present?
@todos << todo
@not_done_todos << todo if p.new_context_created || p.new_project_created
@predecessor = todo
end
else
@todos = @build_todos
@saved = false
end
respond_to do |format|
format.html { redirect_to :action => "index" }
format.js do
determine_down_count if @saved
@contexts = current_user.contexts if p.new_context_created
@projects = current_user.projects if p.new_project_created
@new_project_created = p.new_project_created
@new_context_created = p.new_context_created
@initial_context_name = params['default_context_name']
@initial_project_name = params['default_project_name']
@initial_tags = params['initial_tag_list']
if @saved && @todos.size > 0
@default_tags = @todos[0].project.default_tags unless @todos[0].project.nil?
else
@multiple_error = @todos.size > 0 ? "" : t('todos.next_action_needed')
@saved = false
@default_tags = current_user.projects.where(:name => @initial_project_name).default_tags if @initial_project_name.present?
end
@status_message = @todos.size > 1 ? t('todos.added_new_next_action_plural') : t('todos.added_new_next_action_singular')
@status_message = t('todos.added_new_project') + ' / ' + @status_message if p.new_project_created
@status_message = t('todos.added_new_context') + ' / ' + @status_message if p.new_context_created
render :action => 'create_multiple'
end
format.xml do
if @saved
head :created, :location => context_url(@todos[0].context)
else
render :xml => @todos[0].errors.to_xml, :status => 422
end
end
end
end
def edit
@todo = current_user.todos.find(params['id'])
@source_view = params['_source_view'] || 'todo'
@tag_name = params['_tag_name']
respond_to do |format|
format.js
format.m {
@projects = current_user.projects.active
@contexts = current_user.contexts
@edit_mobile = true
@return_path=cookies[:mobile_url] ? cookies[:mobile_url] : mobile_path
}
end
end
def show
@todo = current_user.todos.find(params['id'])
respond_to do |format|
format.m { render :action => 'show' }
format.xml { render :xml => @todo.to_xml( *todo_xml_params ) }
end
end
def add_predecessor
@source_view = params['_source_view'] || 'todo'
@predecessor = current_user.todos.find(params['predecessor'])
@predecessors = @predecessor.predecessors
@todo = current_user.todos.includes(Todo::DEFAULT_INCLUDES).find(params['successor'])
@original_state = @todo.state
unless @predecessor.completed?
begin
@todo.add_predecessor(@predecessor)
@todo.block! unless @todo.pending?
@saved = @todo.save
rescue ActiveRecord::RecordInvalid
@saved = false
end
@status_message = t('todos.added_dependency', :dependency => @predecessor.description)
@status_message += t('todos.set_to_pending', :task => @todo.description) unless @original_state == 'pending'
else
@saved = false
end
respond_to do |format|
format.js
end
end
def remove_predecessor
@source_view = params['_source_view'] || 'todo'
@todo = current_user.todos.includes(Todo::DEFAULT_INCLUDES).find(params['id'])
@predecessor = current_user.todos.find(params['predecessor'])
@predecessors = @predecessor.predecessors
@successor = @todo
@removed = @successor.remove_predecessor(@predecessor)
determine_remaining_in_container_count(@todo)
respond_to do |format|
format.js
end
end
# Toggles the 'done' status of the action
#
def toggle_check
@todo = current_user.todos.find(params['id'])
@source_view = params['_source_view'] || 'todo'
@original_item = current_user.todos.build(@todo.attributes) # create a (unsaved) copy of the original todo
@original_item_due = @todo.due
@original_item_was_deferred = @todo.deferred?
@original_item_was_pending = @todo.pending?
@original_item_was_hidden = @todo.hidden?
@original_item_context_id = @todo.context_id
@original_item_project_id = @todo.project_id
@original_completed_period = DoneTodos.completed_period(@todo.completed_at)
@todo_was_completed_from_deferred_or_blocked_state = @original_item_was_deferred || @original_item_was_pending
@saved = @todo.toggle_completion!
@todo_was_blocked_from_completed_state = @todo.pending? # since we toggled_completion the previous state was completed
# check if this todo has a related recurring_todo. If so, create next todo
@new_recurring_todo = check_for_next_todo(@todo) if @saved
@predecessors = @todo.uncompleted_predecessors
if @saved
if @todo.completed?
@pending_to_activate = @todo.activate_pending_todos
else
@active_to_block = @todo.block_successors
end
end
respond_to do |format|
format.js do
if @saved
determine_remaining_in_container_count(@todo)
determine_down_count
determine_completed_count
determine_deferred_tag_count(params['_tag_name']) if source_view_is(:tag)
@wants_redirect_after_complete = @todo.completed? && !@todo.project_id.nil? && current_user.prefs.show_project_on_todo_done && !source_view_is(:project)
if source_view_is :calendar
@original_item_due_id = get_due_id_for_calendar(@original_item_due)
@old_due_empty = is_old_due_empty(@original_item_due_id)
end
end
render
end
format.xml { render :xml => @todo.to_xml( *todo_xml_params ) }
format.html do
if @saved
# TODO: I think this will work, but can't figure out how to test it
notify(:notice, t("todos.action_marked_complete", :description => @todo.description, :completed => @todo.completed? ? 'complete' : 'incomplete'))
redirect_to :action => "index"
else
notify(:notice, t("todos.action_marked_complete_error", :description => @todo.description, :completed => @todo.completed? ? 'complete' : 'incomplete'), "index")
redirect_to :action => "index"
end
end
format.m {
if @saved
if cookies[:mobile_url]
old_path = cookies[:mobile_url]
cookies[:mobile_url] = {:value => nil, :secure => SITE_CONFIG['secure_cookies']}
notify(:notice, t("todos.action_marked_complete", :description => @todo.description, :completed => @todo.completed? ? 'complete' : 'incomplete'))
onsite_redirect_to old_path
else
notify(:notice, t("todos.action_marked_complete", :description => @todo.description, :completed => @todo.completed? ? 'complete' : 'incomplete'))
onsite_redirect_to todos_path(:format => 'm')
end
else
render :action => "edit", :format => :m
end
}
end
end
def toggle_star
@todo = current_user.todos.find(params['id'])
@todo.toggle_star!
@saved = true # cannot determine error
respond_to do |format|
format.js
format.xml { render :xml => @todo.to_xml( *todo_xml_params ) }
format.html { redirect_to request.referrer}
format.m {
if cookies[:mobile_url]
old_path = cookies[:mobile_url]
cookies[:mobile_url] = {:value => nil, :secure => SITE_CONFIG['secure_cookies']}
notify(:notice, "Star toggled")
onsite_redirect_to old_path
else
notify(:notice, "Star toggled")
onsite_redirect_to todos_path(:format => 'm')
end
}
end
end
def change_context
# change context if you drag a todo to another context
@todo = current_user.todos.find(params[:id])
@original_item_context_id = @todo.context_id
@original_item = current_user.todos.build(@todo.attributes) # create a (unsaved) copy of the original todo
@context = current_user.contexts.find(params[:todo][:context_id])
@todo.context = @context
@saved = @todo.save
@context_changed = true
@status_message = t('todos.context_changed', :name => @context.name)
determine_down_count
determine_remaining_in_container_count(@original_item)
respond_to do |format|
format.js { render :action => :update }
format.xml { render :xml => @todo.to_xml( *todo_xml_params ) }
end
end
def update
@source_view = params['_source_view'] || 'todo'
@todo = current_user.todos.find(params['id'])
@original_item = current_user.todos.build(@todo.attributes) # create a (unsaved) copy of the original todo
cache_attributes_from_before_update # TODO: remove in favor of @original_item
update_tags
update_project
update_context
update_due_and_show_from_dates
update_completed_state
update_dependencies
update_attributes_of_todo
begin
@saved = @todo.save!
rescue ActiveRecord::RecordInvalid => exception
record = exception.record
if record.is_a?(Dependency)
record.errors.each { |key,value| @todo.errors[key] << value }
end
@saved = false
end
provide_project_or_context_for_view
# this is set after save and cleared after reload, so save it here
@removed_predecessors = @todo.removed_predecessors
@todo.reload # refresh context and project object too (not only their id's)
update_dependency_state
update_todo_state_if_project_changed
determine_changes_by_this_update
determine_remaining_in_container_count( (@context_changed || @project_changed) ? @original_item : @todo)
determine_down_count
determine_deferred_tag_count(sanitize(params['_tag_name'])) if source_view_is(:tag)
@todo.touch_predecessors if @original_item_description != @todo.description
respond_to do |format|
format.js {
@status_message = @todo.deferred? ? t('todos.action_saved_to_tickler') : t('todos.action_saved')
@status_message = t('todos.added_new_project') + ' / ' + @status_message if @new_project_created
@status_message = t('todos.added_new_context') + ' / ' + @status_message if @new_context_created
}
format.xml { render :xml => @todo.to_xml( *todo_xml_params ) }
format.m do
if @saved
do_mobile_todo_redirection
else
render :action => "edit", :format => :m
end
end
end
end
def provide_project_or_context_for_view
# see application_helper:source_view_key, used in shown partials
if source_view_is :project
@project = @todo.project
elsif source_view_is :context
@context = @todo.context
end
end
def destroy
@source_view = params['_source_view'] || 'todo'
@todo = current_user.todos.find(params['id'])
@original_item = current_user.todos.build(@todo.attributes) # create a (unsaved) copy of the original todo
@original_item_due = @todo.due
@context_id = @todo.context_id
@project_id = @todo.project_id
@todo_was_destroyed = true
@todo_was_destroyed_from_deferred_state = @todo.deferred?
@todo_was_destroyed_from_pending_state = @todo.pending?
@todo_was_destroyed_from_deferred_or_pending_state = @todo_was_destroyed_from_deferred_state || @todo_was_destroyed_from_pending_state
@uncompleted_predecessors = []
@todo.uncompleted_predecessors.each do |predecessor|
@uncompleted_predecessors << predecessor
end
# activate successors if they only depend on this todo
activated_successor_count = 0
@pending_to_activate = []
@todo.pending_successors.each do |successor|
successor.uncompleted_predecessors.delete(@todo)
if successor.uncompleted_predecessors.empty?
successor.activate!
@pending_to_activate << successor
activated_successor_count += 1
end
end
@saved = @todo.destroy
# check if this todo has a related recurring_todo. If so, create next todo
@new_recurring_todo = check_for_next_todo(@todo) if @saved
respond_to do |format|
format.html do
if @saved
message = t('todos.action_deleted_success')
if activated_successor_count > 0
message += " activated #{pluralize(activated_successor_count, 'pending action')}"
end
notify :notice, message, 2.0
redirect_to :action => 'index'
else
notify :error, t('todos.action_deleted_error'), 2.0
redirect_to :action => 'index'
end
end
format.js do
if @saved
determine_down_count
if source_view_is_one_of(:todo, :deferred, :project, :context, :tag)
determine_remaining_in_container_count(@todo)
elsif source_view_is :calendar
@original_item_due_id = get_due_id_for_calendar(@original_item_due)
@old_due_empty = is_old_due_empty(@original_item_due_id)
end
end
render
end
format.xml { render :text => '200 OK. Action deleted.', :status => 200 }
end
end
def done
@source_view = 'done'
@page_title = t('todos.completed_tasks_title')
@done_today, @done_rest_of_week, @done_rest_of_month = DoneTodos.done_todos_for_container(current_user.todos)
@count = @done_today.size + @done_rest_of_week.size + @done_rest_of_month.size
respond_to do |format|
format.html
format.xml do
completed_todos = current_user.todos.completed
render :xml => completed_todos.to_xml( *todo_xml_params )
end
end
end
def all_done
@source_view = 'done'
@page_title = t('todos.completed_tasks_title')
@done = current_user.todos.completed.includes(Todo::DEFAULT_INCLUDES).reorder('completed_at DESC').paginate :page => params[:page], :per_page => 20
@count = @done.size
end
def list_deferred
@source_view = 'deferred'
@page_title = t('todos.deferred_tasks_title')
@contexts_to_show = @contexts = current_user.contexts
@projects_to_show = @projects = current_user.projects
includes = params[:format]=='xml' ? [:context, :project] : Todo::DEFAULT_INCLUDES
@not_done_todos = current_user.todos.deferred.includes(includes).reorder('show_from') + current_user.todos.pending.includes(includes)
@todos_without_project = @not_done_todos.select{|t|t.project.nil?}
@down_count = @count = @not_done_todos.size
respond_to do |format|
format.html do
init_not_done_counts
init_project_hidden_todo_counts
init_data_for_sidebar unless mobile?
end
format.m
format.xml { render :xml => @not_done_todos.to_xml( *todo_xml_params ) }
end
end
# Check for any due tickler items, activate them
# Called by periodically_call_remote
def check_deferred
@due_tickles = current_user.deferred_todos.find_and_activate_ready
respond_to do |format|
format.html { redirect_to home_path }
format.js
end
end
def filter_to_context
context = current_user.contexts.find(params['context']['id'])
redirect_to context_todos_path(context, :format => 'm')
end
def filter_to_project
project = current_user.projects.find(params['project']['id'])
redirect_to project_todos_path(project, :format => 'm')
end
# /todos/tag/[tag_name] shows all the actions tagged with tag_name
def tag
get_params_for_tag_view
@page_title = t('todos.tagged_page_title', :tag_name => @tag_title)
@source_view = params['_source_view'] || 'tag'
init_data_for_sidebar unless mobile?
todos_with_tag_ids = find_todos_with_tag_expr(@tag_expr)
@not_done_todos = todos_with_tag_ids.
active.not_hidden.
reorder('todos.due IS NULL, todos.due ASC, todos.created_at ASC').
includes(Todo::DEFAULT_INCLUDES)
@hidden_todos = todos_with_tag_ids.
hidden.
reorder('todos.completed_at DESC, todos.created_at DESC').
includes(Todo::DEFAULT_INCLUDES)
@deferred_todos = todos_with_tag_ids.
deferred.
reorder('todos.show_from ASC, todos.created_at DESC').
includes(Todo::DEFAULT_INCLUDES)
@pending_todos = todos_with_tag_ids.
blocked.
reorder('todos.show_from ASC, todos.created_at DESC').
includes(Todo::DEFAULT_INCLUDES)
@todos_without_project = @not_done_todos.select{|t| t.project.nil?}
# If you've set no_completed to zero, the completed items box isn't shown on
# the tag page
@done = todos_with_tag_ids.completed.
limit(current_user.prefs.show_number_completed).
reorder('todos.completed_at DESC').
includes(Todo::DEFAULT_INCLUDES)
@projects = current_user.projects
@contexts = current_user.contexts
@contexts_to_show = @contexts.active
@projects_to_show = @projects.active
# Set defaults for new_action
@initial_tags = @tag_name
# Set count badge to number of items with this tag
@not_done_todos.empty? ? @count = 0 : @count = @not_done_todos.size
@down_count = @count
respond_to do |format|
format.html
format.m {
cookies[:mobile_url]= {:value => request.fullpath, :secure => SITE_CONFIG['secure_cookies']}
}
format.text {
render :action => 'index', :layout => false, :content_type => Mime::TEXT
}
end
end
def done_tag
done_by_tag_setup
completed_todos = current_user.todos.completed.with_tag(@tag.id)
@done_today = get_done_today(completed_todos)
@done_rest_of_week = get_done_rest_of_week(completed_todos)
@done_rest_of_month = get_done_rest_of_month(completed_todos)
@count = @done_today.size + @done_rest_of_week.size + @done_rest_of_month.size
render :template => 'todos/done'
end
def all_done_tag
done_by_tag_setup
@done = current_user.todos.completed.with_tag(@tag.id).reorder('completed_at DESC').includes(Todo::DEFAULT_INCLUDES).paginate :page => params[:page], :per_page => 20
@count = @done.size
render :template => 'todos/all_done'
end
def done_by_tag_setup
@source_view = params['_source_view'] || 'done'
@tag_name = sanitize(params[:name]) # sanitize to prevent XSS vunerability!
@page_title = t('todos.all_completed_tagged_page_title', :tag_name => @tag_name)
@tag = Tag.where(:name => @tag_name).first_or_create
end
def tags
# TODO: limit to current_user
tags_beginning = Tag.where('name like ?', params[:term]+'%')
tags_all = Tag.where('name like ?', '%'+params[:term]+'%')
tags_all= tags_all - tags_beginning
respond_to do |format|
format.autocomplete { render :text => for_autocomplete(tags_beginning+tags_all, params[:term]) }
end
end
def defer
@source_view = params['_source_view'] || 'todo'
numdays = params['days'].to_i
@todo = current_user.todos.find(params[:id])
@original_item = current_user.todos.build(@todo.attributes) # create a (unsaved) copy of the original todo
@original_item_context_id = @todo.context_id
@todo_deferred_state_changed = true
@new_context_created = false
@due_date_changed = false
@tag_was_removed = false
@todo_hidden_state_changed = false
@todo_was_deferred_from_active_state = @todo.show_from.nil?
@todo.show_from = (@todo.show_from || @todo.user.date) + numdays.days
@saved = @todo.save
@status_message = t('todos.action_saved_to_tickler')
determine_down_count
determine_remaining_in_container_count(@todo)
source_view do |page|
page.project {
@remaining_undone_in_project = current_user.projects.find(@todo.project_id).todos.not_completed.count
@original_item_project_id = @todo.project_id
}
page.tag {
determine_deferred_tag_count(params['_tag_name'])
}
end
respond_to do |format|
format.html { redirect_to :back }
format.js {render :action => 'update'}
format.m {
notify(:notice, t("todos.action_deferred", :description => @todo.description))
do_mobile_todo_redirection
}
end
end
def list_hidden
@hidden = current_user.todos.hidden
respond_to do |format|
format.xml {
render :xml => @hidden.to_xml( *todo_xml_params )
}
end
end
def get_not_completed_for_predecessor(relation, todo_id=nil)
items = relation.todos.not_completed.
where('(LOWER(todos.description) LIKE ?)', "%#{params[:term].downcase}%")
items = items.where("AND NOT(todos.id=?)", todo_id) unless todo_id.nil?
items.
includes(:context, :project).
reorder('description ASC').
limit(10)
end
def auto_complete_for_predecessor
unless params['id'].nil?
get_todo_from_params
# Begin matching todos in current project, excluding @todo itself
@items = get_not_completed_for_predecessor(@todo.project, @todo.id) unless @todo.project.nil?
# Then look in the current context, excluding @todo itself
@items = get_not_completed_for_predecessor(@todo.context, @todo.id) unless !@items.empty? || @todo.context.nil?
# Match todos in other projects, excluding @todo itself
@items = get_not_completed_for_predecessor(current_user, @todo.id) unless !@items.empty?
else
# New todo - TODO: Filter on current project in project view
@items = get_not_completed_for_predecessor(current_user)
end
render :inline => format_dependencies_as_json_for_auto_complete(@items)
end
def convert_to_project
todo = current_user.todos.find(params[:id])
@project = ProjectFromTodo.new(todo).create
if @project.valid?
redirect_to project_url(@project)
else
flash[:error] = "Could not create project from todo: #{@project.errors.full_messages[0]}"
onsite_redirect_to request.env["HTTP_REFERER"] || root_url
end
end
def show_notes
@todo = current_user.todos.find(params['id'])
@return_path=cookies[:mobile_url] ? cookies[:mobile_url] : mobile_path
respond_to do |format|
format.html {
redirect_to home_path, "Viewing note of todo is not implemented"
}
format.m {
render :action => "show_notes"
}
end
end
def attachment
id = params[:id]
filename = params[:filename]
attachment = current_user.attachments.find(id)
if attachment
send_file(attachment.file.path,
disposition: 'attachment',
type: 'message/rfc822')
else
head :not_found
end
end
private
def set_group_view_by
@group_view_by = params['_group_view_by'] || cookies['group_view_by'] || 'context'
end
def do_mobile_todo_redirection
if cookies[:mobile_url]
old_path = cookies[:mobile_url]
cookies[:mobile_url] = {:value => nil, :secure => SITE_CONFIG['secure_cookies']}
onsite_redirect_to old_path
else
onsite_redirect_to todos_path(:format => 'm')
end
end
def get_todo_from_params
# TODO: this was a :append_before but was removed to tune performance per
# method. Reconsider re-enabling it
@todo = current_user.todos.find(params['id'])
end
def find_and_activate_ready
current_user.deferred_todos.find_and_activate_ready
end
def tag_title(tag_expr)
and_list = tag_expr.inject([]) { |s,tag_list| s << tag_list.join(',') }
return and_list.join(' AND ')
end
def get_params_for_tag_view
filter_format_for_tag_view
# use sanitize to prevent XSS attacks
@tag_expr = []
@tag_expr << sanitize(params[:name]).split(',')
@tag_expr << sanitize(params[:and]).split(',') if params[:and]
i = 1
while params['and'+i.to_s]
@tag_expr << sanitize(params['and'+i.to_s]).split(',')
i=i+1
end
@single_tag = @tag_expr.size == 1 && @tag_expr[0].size == 1
@tag_name = @tag_expr[0][0]
@tag_title = @single_tag ? @tag_name : tag_title(@tag_expr)
end
def filter_format_for_tag_view
# routes for tag view do not set :format
if params[:name] =~ /.*\.m$/
set_format_for_tag_view(:m)
elsif params[:name] =~ /.*\.txt$/
set_format_for_tag_view(:txt)
# set content-type to text/plain or it remains text/html
response.headers["Content-Type"] = 'text/plain'
elsif params[:format].nil?
# if no format is given, default to html
# note that if url has ?format=m, we should not overwrite it here
request.format = :html
params[:format] = :html
end
end
def set_format_for_tag_view(format)
# tag name ends with .m, set format to :m en remove .m from name
request.format = format
params[:format] = format
params[:name] = params[:name].chomp(".#{format.to_s}")
end
def get_ids_from_tag_expr(tag_expr)
ids = []
tag_expr.each do |tag_list|
id_list = []
tag_list.each do |tag|
tag = Tag.where(:name => tag).first
id_list << tag.id if tag
end
ids << id_list
end
return ids
end
def find_todos_with_tag_expr(tag_expr)
# optimize for the common case: selecting only one tag
if @single_tag
tag = Tag.where(:name => @tag_name).first
tag_id = tag.nil? ? -1 : tag.id
return current_user.todos.with_tag(tag_id)
end
tag_ids = get_ids_from_tag_expr(tag_expr)
todos = current_user.todos
tag_ids.each do |ids|
todos = todos.with_tags(ids) unless ids.nil? || ids.empty?
end
return todos
end
def determine_down_count
source_view do |from|
from.todo do
@down_count = current_user.todos.active.not_hidden.count
end
from.context do
context_id = @original_item_context_id || @todo.context_id
todos = current_user.contexts.find(context_id).todos.not_completed
if @todo.context.hidden?
# include hidden todos
@down_count = todos.count
else
# exclude hidden_todos
@down_count = todos.not_hidden.count
end
end
from.project do
unless @todo.project_id == nil
@down_count = current_user.projects.find(@todo.project_id).todos.active_or_hidden.count
end
end
from.deferred do
@down_count = current_user.todos.deferred_or_blocked.count
end
from.tag do
@tag_name = params['_tag_name']
@tag = Tag.where(:name => @tag_name).first
if @tag.nil?
@tag = Tag.new(:name => @tag_name)
end
@down_count = current_user.todos.with_tag(@tag.id).active.not_hidden.count
end
end
end
def find_todos_in_project_container(todo)
if todo.project.nil?
# container with todos without project
todos_in_container = current_user.todos.where(:project_id => nil)
else
todos_in_container = current_user.projects.find(todo.project_id).todos
end
end
def find_todos_in_container_and_target_container(todo, target_todo)
if @group_view_by == 'context'
todos_in_container = current_user.contexts.find(todo.context_id).todos
todos_in_target_container = current_user.contexts.find(@todo.context_id).todos
else
todos_in_container = find_todos_in_project_container(todo)
todos_in_target_container = find_todos_in_project_container(@todo)
end
return todos_in_container, todos_in_target_container
end
def determine_remaining_in_container_count(todo = @todo)
source_view do |from|
from.deferred {
todos_in_container, todos_in_target_container = find_todos_in_container_and_target_container(todo, @todo)
@remaining_in_context = todos_in_container.deferred_or_blocked.count
@target_context_count = todos_in_target_container.deferred_or_blocked.count
}
from.todo {
todos_in_container, todos_in_target_container = find_todos_in_container_and_target_container(todo, @todo)
@remaining_in_context = todos_in_container.active.not_hidden.count
@target_context_count = todos_in_target_container.active.not_hidden.count
}
from.tag {
tag = Tag.where(:name => params['_tag_name']).first
tag = Tag.new(:name => params['tag']) if tag.nil?
todos_in_container, todos_in_target_container = find_todos_in_container_and_target_container(todo, @todo)
@remaining_in_context = todos_in_container.active.not_hidden.with_tag(tag.id).count
@target_context_count = todos_in_target_container.active.not_hidden.with_tag(tag.id).count
@remaining_hidden_count = current_user.todos.hidden.with_tag(tag.id).count
@remaining_deferred_or_pending_count = current_user.todos.with_tag(tag.id).deferred_or_blocked.count
}
from.project {
project_id = @project_changed ? @original_item_project_id : @todo.project_id
@remaining_deferred_or_pending_count = current_user.projects.find(project_id).todos.deferred_or_blocked.count
if @todo_was_completed_from_deferred_or_blocked_state
@remaining_in_context = @remaining_deferred_or_pending_count
else
@remaining_in_context = current_user.projects.find(project_id).todos.active_or_hidden.count
end
@target_context_count = current_user.projects.find(project_id).todos.active.count
}
from.calendar {
@target_context_count = @new_due_id.blank? ? 0 : count_old_due_empty(@new_due_id)
}
from.context {
context = current_user.contexts.find(todo.context_id)
@remaining_deferred_or_pending_count = context.todos.deferred_or_blocked.count
remaining_actions_in_context = context.todos(true).active
remaining_actions_in_context = remaining_actions_in_context.not_hidden if !context.hidden?
@remaining_in_context = remaining_actions_in_context.count
if @todo_was_deferred_or_blocked
actions_in_target = current_user.contexts.find(@todo.context_id).todos(true).active
actions_in_target = actions_in_target.not_hidden if !context.hidden?
else
actions_in_target = @todo.context.todos.deferred_or_blocked
end
@target_context_count = actions_in_target.count
}
from.done {
@remaining_in_context = DoneTodos.remaining_in_container(current_user.todos, @original_completed_period)
}
from.all_done {
@remaining_in_context = current_user.todos.completed.count
}
end
end
def find_completed(relation, id, include_hidden)
todos = relation.find(id).todos.completed
todos = todos.not_hidden if !include_hidden
return todos
end
def determine_completed_count
todos=nil
source_view do |from|
from.todo { todos = current_user.todos.not_hidden.completed }
from.context { todos = find_completed(current_user.contexts, @todo.context_id, @todo.context.hidden?) }
from.project { todos = find_completed(current_user.projects, @todo.project_id, @todo.project.hidden?) unless @todo.project_id.nil? }
from.tag { todos = current_user.todos.with_tag(@tag.id).completed }
end
@completed_count = todos.nil? ? 0 : todos.count
end
def determine_deferred_tag_count(tag_name)
tag = Tag.where(:name => tag_name).first
# tag.nil? should normally not happen, but is a workaround for #929
@remaining_deferred_or_pending_count = tag.nil? ? 0 : current_user.todos.deferred.with_tag(tag.id).count
end
def check_for_next_todo(todo)
# check if this todo has a related recurring_todo. If so, create next todo
new_recurring_todo = nil
recurring_todo = nil
if todo.from_recurring_todo?
recurring_todo = todo.recurring_todo
# check if there are active todos belonging to this recurring todo. only
# add new one if all active todos are completed
if recurring_todo.todos.active.count == 0
# check for next todo either from the due date or the show_from date
date_to_check = todo.due || todo.show_from
# if both due and show_from are nil, check for a next todo from now
date_to_check ||= Time.zone.now
if recurring_todo.active? && recurring_todo.continues_recurring?(date_to_check)
# shift the reference date to yesterday if date_to_check is furher in
# the past. This is to make sure we do not get older todos for overdue
# todos. I.e. checking a daily todo that is overdue with 5 days will
# create a new todo which is overdue by 4 days if we don't shift the
# date. Discard the time part in the compare. We pick yesterday so
# that new todos due for today will be created instead of new todos
# for tomorrow.
date = date_to_check.at_midnight >= Time.zone.now.at_midnight ? date_to_check : Time.zone.now-1.day
new_recurring_todo = TodoFromRecurringTodo.new(current_user, recurring_todo).create(date)
end
end
end
return new_recurring_todo
end
def get_due_id_for_calendar(due)
return "" if due.nil?
due_today_date = Time.zone.now
due_this_week_date = Time.zone.now.end_of_week
due_next_week_date = due_this_week_date + 7.days
due_this_month_date = Time.zone.now.end_of_month
if due <= due_today_date
new_due_id = "due_today"
elsif due <= due_this_week_date
new_due_id = "due_this_week"
elsif due <= due_next_week_date
new_due_id = "due_next_week"
elsif due <= due_this_month_date
new_due_id = "due_this_month"
else
new_due_id = "due_after_this_month"
end
return new_due_id
end
def is_old_due_empty(id)
return 0 == count_old_due_empty(id)
end
def count_old_due_empty(id)
due_today_date = Time.zone.now
due_this_week_date = Time.zone.now.end_of_week
due_next_week_date = due_this_week_date + 7.days
due_this_month_date = Time.zone.now.end_of_month
case id
when "due_today"
return current_user.todos.not_completed.where('todos.due <= ?', due_today_date).count
when "due_this_week"
return current_user.todos.not_completed.where('todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date).count
when "due_next_week"
return current_user.todos.not_completed.where('todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date).count
when "due_this_month"
return current_user.todos.not_completed.where('todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date).count
when "due_after_this_month"
return current_user.todos.not_completed.where('todos.due > ?', due_this_month_date).count
else
raise Exception.new, "unknown due id for calendar: '#{id}'"
end
end
def cache_attributes_from_before_update
@original_item_context_id = @todo.context_id
@original_item_project_id = @todo.project_id
@original_item_was_deferred = @todo.deferred?
@original_item_was_hidden = @todo.hidden?
@original_item_was_pending = @todo.pending?
@original_item_due = @todo.due
@original_item_due_id = get_due_id_for_calendar(@todo.due)
@original_item_predecessor_list = @todo.predecessors.map{|t| t.specification}.join(', ')
@original_item_description = @todo.description
@todo_was_deferred_or_blocked = @todo.deferred? || @todo.pending?
end
def update_project
@project_changed = false
if params['todo']['project_id'].blank? && !params['project_name'].nil?
if params['project_name'].blank?
project = Project.null_object
else
project = current_user.projects.where(:name => params['project_name'].strip).first
unless project
project = current_user.projects.build
project.name = params['project_name'].strip
project.save
@new_project_created = true
@new_container = project
@not_done_todos = [@todo]
end
end
params["todo"]["project_id"] = project.id
@project_changed = @original_item_project_id != params["todo"]["project_id"] = project.id
end
end
def update_todo_state_if_project_changed
if @project_changed
@todo.update_state_from_project
@remaining_undone_in_project = current_user.projects.find(@original_item_project_id).todos.active.count if source_view_is :project
end
end
def update_context
@context_changed = false
if params['todo']['context_id'].blank? && params['context_name'].present?
@context = current_user.contexts.where(:name => params['context_name'].strip).first
if @context.nil?
@new_context = current_user.contexts.build
@new_context.name = params['context_name'].strip
@new_context.save
@new_context_created = true
@new_container = @new_context
@not_done_todos = [@todo]
@context = @new_context
end
params["todo"]["context_id"] = @context.id
@context_changed = @original_item_context_id != params["todo"]["context_id"] = @context.id
end
end
def update_tags
if params[:tag_list]
@todo.tag_with(params[:tag_list])
@todo.tags(true) #force a reload for proper rendering
end
end
def parse_date_for_update(date, error_msg)
begin
parse_date_per_user_prefs(date)
rescue
@todo.errors[:base] << error_msg
end
end
def update_date_for_update(key)
params['todo'][key] = params["todo"].has_key?(key) ? parse_date_for_update(params["todo"][key], t("todos.error.invalid_#{key}_date")) : ""
end
def update_due_and_show_from_dates
%w{ due show_from }.each {|date| update_date_for_update(date) }
end
def update_completed_state
if params['done'] == '1' && !@todo.completed?
@todo.complete!
@todo.activate_pending_todos
end
# strange. if checkbox is not checked, there is no 'done' in params.
# Therefore I've used the negation
if !(params['done'] == '1') && @todo.completed?
@todo.activate!
@todo.block_successors
end
end
def update_dependencies
@todo.add_predecessor_list(params[:predecessor_list])
end
def update_dependency_state
# assumes @todo.save was called so that the predecessor_list is persistent
if @original_item_predecessor_list != params[:predecessor_list]
# Possible state change with new dependencies
if @todo.uncompleted_predecessors.empty?
@todo.activate! if @todo.state == 'pending' # Activate pending if no uncompleted predecessors
else
@todo.block! if @todo.state == 'active' # Block active if we got uncompleted predecessors
end
end
end
def update_attributes_of_todo
# TODO: duplication with todo_create_params_helper
@todo.attributes = params.require(:todo).permit(
:context_id, :project_id, :description, :notes,
:due, :show_from, :state)
end
def determine_changes_by_this_update
@todo_was_activated_from_deferred_state = @todo.active? && @original_item_was_deferred
@todo_was_activated_from_pending_state = @todo.active? && @original_item_was_pending
@todo_was_deferred_from_active_state = @todo.deferred? && !@original_item_was_deferred
@todo_was_blocked_from_active_state = @todo.pending? && !@original_item_was_pending
@todo_deferred_state_changed = @original_item_was_deferred != @todo.deferred?
@todo_pending_state_changed = @original_item_was_pending != @todo.pending?
@todo_hidden_state_changed = @original_item_was_hidden != @todo.hidden?
@due_date_changed = @original_item_due != @todo.due
source_view do |page|
page.calendar do
@old_due_empty = is_old_due_empty(@original_item_due_id)
@new_due_id = get_due_id_for_calendar(@todo.due)
end
page.tag do
@tag_name = params['_tag_name']
@tag_was_removed = !@todo.has_tag?(@tag_name)
end
page.context do
@todo_should_be_hidden = @todo_hidden_state_changed && @todo.hidden?
end
end
end
# all completed todos [today@00:00, today@now]
def get_done_today(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_day = Time.zone.now.beginning_of_day
completed_todos.completed_after(start_of_this_day).includes(includes[:include])
end
def get_done_in_period(completed_todos, before, after, includes = {:include => Todo::DEFAULT_INCLUDES})
completed_todos.completed_before(before).completed_after(after).includes(includes[:include])
end
# all completed todos [begin_of_week, start_of_today]
def get_done_rest_of_week(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
get_done_in_period(completed_todos, Time.zone.now.beginning_of_day, Time.zone.now.beginning_of_week)
end
# all completed todos [begin_of_month, begin_of_week]
def get_done_rest_of_month(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
get_done_in_period(completed_todos, Time.zone.now.beginning_of_week, Time.zone.now.beginning_of_month)
end
def get_not_done_todos
# TODO: refactor text feed for done todos to todos/done.text, not /todos.text?done=true
if params[:done]
not_done_todos = current_user.todos.completed.completed_after(Time.zone.now - params[:done].to_i.days)
else
not_done_todos = current_user.todos.active.not_hidden
end
not_done_todos = not_done_todos.
reorder("todos.due IS NULL, todos.due ASC, todos.created_at ASC").
includes(Todo::DEFAULT_INCLUDES)
not_done_todos = not_done_todos.limit(sanitize(params[:limit])) if params[:limit]
if params[:due]
due_within_when = Time.zone.now + params['due'].to_i.days
not_done_todos = not_done_todos.where('todos.due <= ?', due_within_when)
end
if params[:tag]
tag = Tag.where(:name => params['tag']).first
not_done_todos = not_done_todos.where('taggings.tag_id = ?', tag.id)
end
if params[:context_id]
context = current_user.contexts.find(params[:context_id])
not_done_todos = not_done_todos.where('context_id' => context.id)
end
if params[:project_id]
project = current_user.projects.find(params[:project_id])
not_done_todos = not_done_todos.where('project_id' => project)
end
return not_done_todos
end
def onsite_redirect_to(destination)
uri = URI.parse(destination)
if uri.query.present?
redirect_to("#{uri.path}?#{uri.query}")
else
redirect_to(uri.path)
end
end
end