class TodosController < ApplicationController helper :todos skip_before_filter :login_required, :only => [:index, :calendar, :tag] prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar, :tag] append_before_filter :find_and_activate_ready, :only => [:index, :list_deferred] # TODO: replace :except with :only append_before_filter :init, :except => [ :tag, :tags, :destroy, :done, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :defer, :create, :calendar, :auto_complete_for_predecessor, :remove_predecessor, :add_predecessor] protect_from_forgery :except => :check_deferred # these are needed for todo_feed_content. TODO: remove this view stuff from controller include ActionView::Helpers::SanitizeHelper extend ActionView::Helpers::SanitizeHelper::ClassMethods def index @projects = current_user.projects.includes(:default_context) @contexts = current_user.contexts @contexts_to_show = current_user.contexts.active respond_to do |format| format.html &render_todos_html format.m &render_todos_mobile format.xml { render :xml => @todos.to_xml( *to_xml_params ) } format.rss &render_rss_feed format.atom &render_atom_feed format.text &render_text_feed format.ics &render_ical_feed 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_by_id(params[:from_context]) if params[:from_context] @mobile_from_project = current_user.projects.find_by_id(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 render :action => "new" } end end def create @source_view = params['_source_view'] || 'todo' @default_context = current_user.contexts.find_by_name(params['default_context_name']) @default_project = current_user.projects.find_by_name(params['default_project_name']) unless params['default_project_name'].blank? @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 = TodoCreateParamsHelper.new(params, prefs) p.parse_dates() unless mobile? tag_list = p.tag_list predecessor_list = p.predecessor_list @todo = current_user.todos.build(p.attributes) if p.project_specified_by_name? project = current_user.projects.find_or_create_by_name(p.project_name) @new_project_created = project.new_record_before_save? @todo.project_id = project.id elsif !(p.project_id.nil? || p.project_id.blank?) project = current_user.projects.find_by_id(p.project_id) @todo.errors.add(:project, "unknown") if project.nil? end if p.context_specified_by_name? context = current_user.contexts.find_or_create_by_name(p.context_name) @new_context_created = context.new_record_before_save? @not_done_todos = [@todo] if @new_context_created @todo.context_id = context.id elsif !(p.context_id.nil? || p.context_id.blank?) context = current_user.contexts.find_by_id(p.context_id) @todo.errors.add(:context, "unknown") if context.nil? end if @todo.errors.empty? @todo.starred= (params[:new_todo_starred]||"").include? "true" if params[:new_todo_starred] @todo.add_predecessor_list(predecessor_list) # Fix for #977 because AASM overrides @state on creation specified_state = @todo.state @saved = @todo.save @todo.update_state_from_project if @saved else @saved = false end unless (@saved == false) || tag_list.blank? @todo.tag_with(tag_list) @todo.tags.reload end if @saved unless @todo.uncompleted_predecessors.empty? || @todo.state == 'project_hidden' @todo.state = 'pending' end @todo.save end @todo.reload if @saved @todo_was_created_deferred = @todo.deferred? @todo_was_created_blocked = @todo.pending? respond_to do |format| format.html { redirect_to :action => "index" } format.m do @return_path=cookies[:mobile_url] ? cookies[:mobile_url] : mobile_path if @saved 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 if @new_context_created @projects = current_user.projects if @new_project_created @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 :xml => @todo.errors.to_xml, :status => 422 end end end end end def create_multiple if project_specified_by_name(params[:project_name]) project = current_user.projects.find_or_create_by_name(params[:project_name]) @new_project_created = project.new_record_before_save? @project_id = project.id end if context_specified_by_name(params[:context_name]) context = current_user.contexts.find_or_create_by_name(params[:context_name]) @new_context_created = context.new_record_before_save? @not_done_todos = [] if @new_context_created @context_id = context.id end tag_list = params[:tag_list] @sequential = !params[:todos_sequential].blank? && params[:todos_sequential]=='true' @todos_init = [] @predecessor = nil validates = true errors = [] # first build all todos and check if they would validate on save params[:todo][:multiple_todos].split("\n").map do |line| unless line.blank? @todo = current_user.todos.build( :description => line) @todo.project_id = @project_id @todo.context_id = @context_id validates = false if @todo.invalid? @todos_init << @todo end end # if all todos validate, then save them and add predecessors and tags @todos = [] if validates @todos_init.each do |todo| @saved = todo.save validates = validates && @saved if @predecessor && @saved && @sequential todo.add_predecessor(@predecessor) todo.block! end unless (@saved == false) || tag_list.blank? todo.tag_with(tag_list) todo.tags.reload end @todos << todo @not_done_todos << todo if @new_context_created @predecessor = todo end else @todos = @todos_init @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 @new_context_created @projects = current_user.projects if @new_project_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.find_by_name(@initial_project_name).default_tags unless @initial_project_name.blank? 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 @new_project_created @status_message = t('todos.added_new_context') + ' / ' + @status_message if @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_by_id(params['id']).includes(Todo::DEFAULT_INCLUDES) @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 render :template => "/todos/edit_mobile.html.erb" } end end def show @todo = current_user.todos.find_by_id(params['id']) respond_to do |format| format.m { render :action => 'show' } format.xml { render :xml => @todo.to_xml( *to_xml_params ) } end end def add_predecessor @source_view = params['_source_view'] || 'todo' @predecessor = current_user.todos.find_by_id(params['predecessor']) @predecessors = @predecessor.predecessors @todo = current_user.todos.find_by_id(params['successor']).includes(Todo::DEFAULT_INCLUDES) @original_state = @todo.state unless @predecessor.completed? @todo.add_predecessor(@predecessor) @todo.block! unless @todo.pending? @saved = @todo.save @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.find_by_id(params['id']).includes(Todo::DEFAULT_INCLUDES) @predecessor = current_user.todos.find_by_id(params['predecessor']) @predecessors = @predecessor.predecessors @successor = @todo @removed = @successor.remove_predecessor(@predecessor) determine_remaining_in_context_count respond_to do |format| format.js end end # Toggles the 'done' status of the action # def toggle_check @todo = current_user.todos.find_by_id(params['id']) @source_view = params['_source_view'] || '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 @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_context_count(@todo.context_id) 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( *to_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')) redirect_to old_path else notify(:notice, t("todos.action_marked_complete", :description => @todo.description, :completed => @todo.completed? ? 'complete' : 'incomplete')) redirect_to todos_path(:format => 'm') end else render :action => "edit", :format => :m end } end end def toggle_star @todo = current_user.todos.find_by_id(params['id']) @todo.toggle_star! @saved = true # cannot determine error respond_to do |format| format.js format.xml { render :xml => @todo.to_xml( *to_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") redirect_to old_path else notify(:notice, "Star toggled") redirect_to todos_path(:format => 'm') end } end end def change_context # TODO: is this method used? @todo = Todo.find_by_id(params[:todo][:id]) @original_item_context_id = @todo.context_id @context = Context.find_by_id(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_context_count(@original_item_context_id) respond_to do |format| format.js { render :action => :update } format.xml { render :xml => @todo.to_xml( *to_xml_params ) } end end def update @todo = current_user.todos.find_by_id(params['id']) @source_view = params['_source_view'] || 'todo' init_data_for_sidebar unless mobile? cache_attributes_from_before_update update_tags update_project update_context update_due_and_show_from_dates update_completed_state update_dependencies update_attributes_of_todo @saved = @todo.save # 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_context_count(@context_changed ? @original_item_context_id : @todo.context_id) determine_down_count determine_deferred_tag_count(params['_tag_name']) if source_view_is(:tag) 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( *to_xml_params ) } format.m do if @saved if cookies[:mobile_url] old_path = cookies[:mobile_url] cookies[:mobile_url] = {:value => nil, :secure => SITE_CONFIG['secure_cookies']} redirect_to old_path else redirect_to todos_path(:format => 'm') end else render :action => "edit", :format => :m end end end end def destroy @source_view = params['_source_view'] || 'todo' @todo = current_user.todos.find_by_id(params['id']) @original_item_due = @todo.due @context_id = @todo.context_id @project_id = @todo.project_id @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) determine_remaining_in_context_count(@context_id) 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') completed_todos = current_user.todos.completed @done_today = get_done_today(completed_todos) @done_this_week = get_done_this_week(completed_todos) @done_this_month = get_done_this_month(completed_todos) @count = @done_today.size + @done_this_week.size + @done_this_month.size respond_to do |format| format.html format.xml { render :xml => completed_todos.to_xml( *to_xml_params ) } end end def all_done @source_view = 'done' @page_title = t('todos.completed_tasks_title') @done = current_user.todos.completed.includes(Todo::DEFAULT_INCLUDES).order('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 includes = params[:format]=='xml' ? [:context, :project] : Todo::DEFAULT_INCLUDES @not_done_todos = current_user.todos.deferred.includes(includes) + current_user.todos.pending.includes(includes) @down_count = @count = @not_done_todos.size respond_to do |format| format.html format.m { render :action => 'mobile_list_deferred' } format.xml { render :xml => @not_done_todos.to_xml( *to_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_by_id(params['context']['id']) redirect_to context_todos_path(context, :format => 'm') end def filter_to_project project = current_user.projects.find_by_id(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' if mobile? # mobile tags are routed with :name ending on .m. So we need to chomp it @tag_name = @tag_name.chomp('.m') else init_data_for_sidebar end todos_with_tag_ids = find_todos_with_tag_expr(@tag_expr) @not_done_todos = todos_with_tag_ids. active.not_hidden. order('todos.due IS NULL, todos.due ASC, todos.created_at ASC'). includes(Todo::DEFAULT_INCLUDES) @hidden_todos = todos_with_tag_ids. hidden. order('todos.completed_at DESC, todos.created_at DESC'). includes(Todo::DEFAULT_INCLUDES) @deferred = todos_with_tag_ids. deferred. order('todos.show_from ASC, todos.created_at DESC'). includes(Todo::DEFAULT_INCLUDES) @pending = todos_with_tag_ids. blocked. order('todos.show_from ASC, todos.created_at DESC'). includes(Todo::DEFAULT_INCLUDES) # 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). order('todos.completed_at DESC'). includes(Todo::DEFAULT_INCLUDES) @projects = current_user.projects @contexts = current_user.contexts @contexts_to_show = @contexts.reject {|x| x.hide? } # Set defaults for new_action @initial_tags = @tag_name unless @not_done_todos.empty? @context = current_user.contexts.find_by_id(@not_done_todos[0].context_id) end # 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.request_uri, :secure => SITE_CONFIG['secure_cookies']} render :action => "mobile_tag" } format.text { render :action => 'index', :layout => false, :content_type => Mime::TEXT } end end def done_tag @source_view = params['_source_view'] || 'tag' @tag_name = sanitize(params[:name]) # sanitize to prevent XSS vunerability! @page_title = t('todos.completed_tagged_page_title', :tag_name => @tag_name) @tag = Tag.find_by_name(@tag_name) @tag = Tag.new(:name => @tag_name) if @tag.nil? completed_todos = current_user.todos.completed.with_tag(@tag.id) @done_today = get_done_today(completed_todos) @done_this_week = get_done_this_week(completed_todos) @done_this_month = get_done_this_month(completed_todos) @count = @done_today.size + @done_this_week.size + @done_this_month.size render :template => 'todos/done' end def all_done_tag @source_view = params['_source_view'] || 'tag' @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.find_by_name(@tag_name) @tag = Tag.new(:name => @tag_name) if @tag.nil? @done = current_user.todos.completed.with_tag(@tag.id).order('completed_at DESC').includes(Todo::DEFAULT_INCLUDES).paginate :page => params[:page], :per_page => 20 @count = @done.size render :template => 'todos/all_done' 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_by_id(params[:id]) @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_context_count(@todo.context_id) source_view do |page| page.project { @remaining_undone_in_project = current_user.projects.find_by_id(@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)) if cookies[:mobile_url] old_path = cookies[:mobile_url] cookies[:mobile_url] = {:value => nil, :secure => SITE_CONFIG['secure_cookies']} redirect_to old_path else redirect_to todos_path(:format => 'm') end } end end def calendar @source_view = params['_source_view'] || 'calendar' @page_title = t('todos.calendar_page_title') @projects = current_user.projects due_today_date = Time.zone.now due_this_week_date = due_today_date.end_of_week due_next_week_date = due_this_week_date + 7.days due_this_month_date = due_today_date.end_of_month included_tables = Todo::DEFAULT_INCLUDES @due_today = current_user.todos.not_completed. where('todos.due <= ?', due_today_date). includes(included_tables). order("due") @due_this_week = current_user.todos.not_completed. where('todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date). includes(included_tables). order("due") @due_next_week = current_user.todos.not_completed. where('todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date). includes(included_tables). order("due") @due_this_month = current_user.todos.not_completed. where('todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date). includes(included_tables). order("due") @due_after_this_month = current_user.todos.not_completed. where('todos.due > ?', due_this_month_date). includes(included_tables). order("due") @count = current_user.todos.not_completed.are_due.count respond_to do |format| format.html format.ics { @due_all = current_user.todos.not_completed.are_due.order("due") render :action => 'calendar', :layout => false, :content_type => Mime::ICS } format.xml { @due_all = current_user.todos.not_completed.are_due.order("due") render :xml => @due_all.to_xml( *to_xml_params ) } end end def list_hidden @hidden = current_user.todos.hidden respond_to do |format| format.xml { render :xml => @hidden.to_xml( *to_xml_params ) } end end def auto_complete_for_predecessor unless params['id'].nil? get_todo_from_params # Begin matching todos in current project, excluding @todo itself @items = @todo.project.todos.not_completed. where('(LOWER(todos.description) LIKE ?) AND NOT(todos.id=?)', "%#{params[:term].downcase}%", @todo.id). includes(:context, :project). order('description ASC'). limit(10) unless @todo.project.nil? # Then look in the current context, excluding @todo itself @items = @todo.context.todos.not_completed where('(LOWER(todos.description) LIKE ?) AND NOT(todos.id=?)', "%#{params[:term].downcase}%", @todo.id). includes(:context, :project). order('description ASC'). limit(10) unless !@items.empty? || @todo.context.nil? # Match todos in other projects, excluding @todo itself @items = current_user.todos.not_completed. where('(LOWER(todos.description) LIKE ?) AND NOT(todos.id=?)', "%#{params[:term].downcase}%", @todo.id). includes(:context, :project). order('description ASC'). limit(10) unless !@items.empty? else # New todo - TODO: Filter on current project in project view @items = current_user.todos.not_completed. where('(LOWER(todos.description) LIKE ?)', "%#{params[:term].downcase}%"). includes(:context, :project). order('description ASC'). limit(10) end render :inline => format_dependencies_as_json_for_auto_complete(@items) end def convert_to_project @todo = current_user.todos.find_by_id(params[:id]) @project = current_user.projects.new(:name => @todo.description, :description => @todo.notes, :default_context => @todo.context) unless @project.invalid? @todo.destroy @project.save! redirect_to project_url(@project) else flash[:error] = "Could not create project from todo: #{@project.errors.full_messages[0]}" redirect_to request.env["HTTP_REFERER"] || root_url end end def show_notes @todo = current_user.todos.find_by_id(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 => "mobile_show_notes" } end end private def to_xml_params if params[:limit_fields] == 'index' return [:only => [:id, :created_at, :updated_at, :completed_at] ] else return [:except => :user_id, :include => [:tags] ] 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_by_id(params['id']) end def find_and_activate_ready current_user.deferred_todos.find_and_activate_ready end def init @source_view = params['_source_view'] || 'todo' init_data_for_sidebar unless mobile? init_todos end def with_feed_query_scope(&block) unless TodosController.is_feed_request(request) Todo.send(:where, ['todos.state = ?', 'active']) do yield return end end condition_builder = FindConditionBuilder.new if params.key?('done') condition_builder.add 'todos.state = ?', 'completed' else condition_builder.add 'todos.state = ?', 'active' end @title = t('todos.next_actions_title') @description = t('todos.next_actions_description') if params.key?('due') due_within = params['due'].to_i due_within_when = Time.zone.now + due_within.days condition_builder.add('todos.due <= ?', due_within_when) due_within_date_s = due_within_when.strftime("%Y-%m-%d") @title << t('todos.next_actions_title_additions.due_today') if (due_within == 0) @title << t('todos.next_actions_title_additions.due_within_a_week') if (due_within == 6) @description << t('todos.next_actions_description_additions.due_date', :due_date => due_within_date_s) end if params.key?('done') done_in_last = params['done'].to_i condition_builder.add('todos.completed_at >= ?', Time.zone.now - done_in_last.days) @title << t('todos.next_actions_title_additions.completed') @description << t('todos.next_actions_description_additions.completed', :count => done_in_last.to_s) end if params.key?('tag') tag = Tag.find_by_name(params['tag']) if tag.nil? tag = Tag.new(:name => params['tag']) end condition_builder.add('taggings.tag_id = ?', tag.id) end Todo.send :where, condition_builder.to_conditions do yield end end def with_parent_resource_scope(&block) @feed_title = t('common.actions') if (params[:context_id]) @context = current_user.contexts.find_by_params(params) @feed_title = @feed_title + t('todos.feed_title_in_context', :context => @context.name) Todo.send :where, ['todos.context_id = ?', @context.id] do yield end elsif (params[:project_id]) @project = current_user.projects.find_by_params(params) @feed_title = @feed_title + t('todos.feed_title_in_project', :project => @project.name) @project_feed = true Todo.send :where, ['todos.project_id = ?', @project.id] do yield end else yield end end def with_limit_scope(&block) if params.key?('limit') Todo.send :with_scope, :find => { :limit => params['limit'] } do yield end if TodosController.is_feed_request(request) && @description if params.key?('limit') @description << t('todos.list_incomplete_next_actions_with_limit', :count => params['limit']) else @description << t('todos.list_incomplete_next_actions') end end else yield end end def init_todos with_feed_query_scope do with_parent_resource_scope do # @context or @project may get defined here with_limit_scope do if mobile? init_todos_for_mobile_view else # Note: these next two finds were previously using # current_users.todos.find but that broke with_scope for :limit # Exclude hidden projects from count on home page @todos = current_user.todos.includes(Todo::DEFAULT_INCLUDES) # Exclude hidden projects from the home page @not_done_todos = current_user.todos. where('contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', false, 'active'). order("todos.due IS NULL, todos.due ASC, todos.created_at ASC"). includes(Todo::DEFAULT_INCLUDES) end end end end end def init_todos_for_mobile_view # Note: these next two finds were previously using current_users.todos.find # but that broke with_scope for :limit # Exclude hidden projects from the home page @not_done_todos = current_user.todos. where('todos.state = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', 'active', false, 'active'). order("todos.due IS NULL, todos.due ASC, todos.created_at ASC"). includes(:project, :context, :tags) 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 # 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 get_ids_from_tag_expr(tag_expr) ids = [] tag_expr.each do |tag_list| id_list = [] tag_list.each do |tag| tag = Tag.find_by_name(tag) 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.find_by_name(@tag_name) 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_by_id(context_id).todos.not_completed if @todo.context.hide? # 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_by_id(@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.find_by_name(@tag_name) 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 determine_remaining_in_context_count(context_id = @todo.context_id) source_view do |from| from.deferred { # force reload to todos to get correct count and not a cached one @remaining_in_context = current_user.contexts.find_by_id(context_id).todos.deferred_or_blocked.count @target_context_count = current_user.contexts.find_by_id(@todo.context_id).todos.deferred_or_blocked.count } from.tag { tag = Tag.find_by_name(params['_tag_name']) if tag.nil? tag = Tag.new(:name => params['tag']) end @remaining_deferred_or_pending_count = current_user.todos.with_tag(tag.id).deferred_or_blocked.count @remaining_in_context = current_user.contexts.find_by_id(context_id).todos.active.not_hidden.with_tag(tag.id).count @target_context_count = current_user.contexts.find_by_id(@todo.context_id).todos.active.not_hidden.with_tag(tag.id).count @remaining_hidden_count = current_user.todos.hidden.with_tag(tag.id).count } from.project { project_id = @project_changed ? @original_item_project_id : @todo.project_id @remaining_deferred_or_pending_count = current_user.projects.find_by_id(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_by_id(project_id).todos.active_or_hidden.count end @target_context_count = current_user.projects.find_by_id(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_by_id(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.hide? @remaining_in_context = remaining_actions_in_context.count if @todo_was_deferred_or_blocked actions_in_target = current_user.contexts.find_by_id(@todo.context_id).todos(true).active actions_in_target = actions_in_target.not_hidden if !context.hide? else actions_in_target = @todo.context.todos.deferred_or_blocked end @target_context_count = actions_in_target.count } end @remaining_in_context = current_user.contexts.find_by_id(context_id).todos(true).active.not_hidden.count if !@remaining_in_context @target_context_count = current_user.contexts.find_by_id(@todo.context_id).todos(true).active.not_hidden.count if !@target_context_count end def determine_completed_count source_view do |from| from.todo do @completed_count = current_user.todos.not_hidden.completed.count end from.context do todos = current_user.contexts.find_by_id(@todo.context_id).todos.completed todos = todos.not_hidden if !@todo.context.hidden? @completed_count = todos.count end from.project do unless @todo.project_id == nil todos = current_user.projects.find_by_id(@todo.project_id).todos.completed todos = todos.not_hidden if !@todo.project.hidden? @completed_count = todos.count end end from.tag do @completed_count = current_user.todos.with_tag(@tag.id).completed.count end end end def determine_deferred_tag_count(tag_name) tag = Tag.find_by_name(tag_name) # 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 render_todos_html lambda do @page_title = t('todos.task_list_title') # 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 # Set count badge to number of not-done, not hidden context items @count = current_user.todos.active.not_hidden.count(:all) render end end def render_todos_mobile lambda do @page_title = t('todos.mobile_todos_page_title') @home = true max_completed = current_user.prefs.show_number_completed @done = current_user.todos.completed.limit(max_completed).includes(Todo::DEFAULT_INCLUDES) unless max_completed == 0 cookies[:mobile_url]= { :value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']} determine_down_count render :action => 'index' end end def render_rss_feed lambda do render_rss_feed_for @todos, :feed => todo_feed_options, :item => { :title => :description, :link => lambda { |t| @project_feed.nil? ? context_url(t.context) : project_url(t.project) }, :guid => lambda { |t| todo_url(t) }, :description => todo_feed_content } end end def todo_feed_options options = Todo.feed_options(current_user) options[:title] = @feed_title return options end def todo_feed_content # TODO: move view stuff into view, also the includes at the top lambda do |i| item_notes = i.rendered_notes if i.notes? due = "