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' end format.text do # somehow passing Mime::TEXT using content_type to render does not work headers['Content-Type']=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) { @feed_title, @feed_description = 'Tracks Actions', "Actions for #{current_user.display_name}" } 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 @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_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 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 unless @not_done_todos.empty? @context = current_user.contexts.find(@not_done_todos.first.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.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 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, params[:format] = :html, :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, params[:format] = 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.nil? ? todo.show_from : todo.due # if both due and show_from are nil, check for a next todo from now date_to_check = Time.zone.now if date_to_check.nil? 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.at_midnight) 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'] == 'None' 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