diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 0d54b75f..8a329f56 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -1,268 +1,268 @@
-# The filters added to this controller will be run for all controllers in the
-# application. Likewise will all the methods added be available for all
-# controllers.
-
-require_dependency "login_system"
-require_dependency "tracks/source_view"
-require "redcloth"
-
-require 'date'
-require 'time'
-
-# Commented the following line because of #744. It prevented rake db:migrate to
-# run because this tag went looking for the taggings table that did not exist
-# when you feshly create a new database Old comment: We need this in development
-# mode, or you get 'method missing' errors
-#
-# Tag
-
-class CannotAccessContext < RuntimeError; end
-
-class ApplicationController < ActionController::Base
-
- protect_from_forgery :secret => SALT
-
- helper :application
- include LoginSystem
- helper_method :current_user, :prefs
-
- layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
-
- before_filter :set_session_expiration
- before_filter :set_time_zone
- prepend_before_filter :login_required
- prepend_before_filter :enable_mobile_content_negotiation
- after_filter :set_charset
-
-
-
- include ActionView::Helpers::TextHelper
- include ActionView::Helpers::SanitizeHelper
- extend ActionView::Helpers::SanitizeHelper::ClassMethods
- helper_method :format_date, :markdown
-
- # By default, sets the charset to UTF-8 if it isn't already set
- def set_charset
- headers["Content-Type"] ||= "text/html; charset=UTF-8"
- end
-
- def set_session_expiration
- # http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
- unless session == nil
- return if @controller_name == 'feed' or session['noexpiry'] == "on"
- # If the method is called by the feed controller (which we don't have
- # under session control) or if we checked the box to keep logged in on
- # login don't set the session expiry time.
- if session
- # Get expiry time (allow ten seconds window for the case where we have
- # none)
- expiry_time = session['expiry_time'] || Time.now + 10
- if expiry_time < Time.now
- # Too late, matey... bang goes your session!
- reset_session
- else
- # Okay, you get another hour
- session['expiry_time'] = Time.now + (60*60)
- end
- end
- end
- end
-
- def render_failure message, status = 404
- render :text => message, :status => status
- end
-
- # def rescue_action(exception)
- # log_error(exception) if logger
- # respond_to do |format|
- # format.html do
- # notify :warning, "An error occurred on the server."
- # render :action => "index"
- # end
- # format.js { render :action => 'error' }
- # format.xml { render :text => 'An error occurred on the server.' + $! }
- # end
- # end
-
- # Returns a count of next actions in the given context or project The result
- # is count and a string descriptor, correctly pluralised if there are no
- # actions or multiple actions
- #
- def count_undone_todos_phrase(todos_parent, string="actions")
- count = count_undone_todos(todos_parent)
- if count == 1
- word = string.singularize
- else
- word = string.pluralize
- end
- return count.to_s + " " + word
- end
-
- def count_undone_todos(todos_parent)
- if todos_parent.nil?
- count = 0
- elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
- count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
- else
- count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
- end
- count || 0
- end
-
- # Convert a date object to the format specified in the user's preferences in
- # config/settings.yml
- #
- def format_date(date)
- if date
- date_format = prefs.date_format
- formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}")
- else
- formatted_date = ''
- end
- formatted_date
- end
-
- # Uses RedCloth to transform text using either Textile or Markdown Need to
- # require redcloth above RedCloth 3.0 or greater is needed to use Markdown,
- # otherwise it only handles Textile
- #
- def markdown(text)
- RedCloth.new(text).to_html
- end
-
- def build_default_project_context_name_map(projects)
- Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
- end
-
- # Here's the concept behind this "mobile content negotiation" hack: In
- # addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature
- # 'mobile' version designed to be suitablef or use from a phone or PDA. It
- # makes some sense that tne pages of that mobile version are simply alternate
- # representations of the same Todo resources. The implementation goal was to
- # treat mobile as another format and be able to use respond_to to render both
- # versions. Unfortunately, I ran into a lot of trouble simply registering a
- # new mime type 'text/html' with format :m because :html already is linked to
- # that mime type and the new registration was forcing all html requests to be
- # rendered in the mobile view. The before_filter and after_filter hackery
- # below accomplishs that implementation goal by using a 'fake' mime type
- # during the processing and then setting it to 'text/html' in an
- # 'after_filter' -LKM 2007-04-01
- def mobile?
- return params[:format] == 'm'
- end
-
- def enable_mobile_content_negotiation
- if mobile?
- request.format = :m
- end
- end
-
- def create_todo_from_recurring_todo(rt, date=nil)
- # create todo and initialize with data from recurring_todo rt
- todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
-
- # set dates
- todo.recurring_todo_id = rt.id
- todo.due = rt.get_due_date(date)
-
- show_from_date = rt.get_show_from_date(date)
- if show_from_date.nil?
- todo.show_from=nil
- else
- # make sure that show_from is not in the past
- todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
- end
-
- saved = todo.save
- if saved
- todo.tag_with(rt.tag_list, current_user)
- todo.tags.reload
- end
-
- # increate number of occurences created from recurring todo
- rt.inc_occurences
-
- # mark recurring todo complete if there are no next actions left
- checkdate = todo.due.nil? ? todo.show_from : todo.due
- rt.toggle_completion! unless rt.has_next_todo(checkdate)
-
- return saved ? todo : nil
- end
-
- protected
-
- def admin_login_required
- unless User.find_by_id_and_is_admin(session['user_id'], true)
- render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401
- return false
- end
- end
-
- def redirect_back_or_home
- respond_to do |format|
- format.html { redirect_back_or_default home_url }
- format.m { redirect_back_or_default mobile_url }
- end
- end
-
- def boolean_param(param_name)
- return false if param_name.blank?
- s = params[param_name]
- return false if s.blank? || s == false || s =~ /^false$/i
- return true if s == true || s =~ /^true$/i
- raise ArgumentError.new("invalid value for Boolean: \"#{s}\"")
- end
-
- def self.openid_enabled?
- Tracks::Config.openid_enabled?
- end
-
- def openid_enabled?
- self.class.openid_enabled?
- end
-
- private
-
- def parse_date_per_user_prefs( s )
- prefs.parse_date(s)
- end
-
- def init_data_for_sidebar
- @completed_projects = current_user.projects.completed
- @hidden_projects = current_user.projects.hidden
- @active_projects = current_user.projects.active
-
- @active_contexts = current_user.contexts.active
- @hidden_contexts = current_user.contexts.hidden
-
- init_not_done_counts
- if prefs.show_hidden_projects_in_sidebar
- init_project_hidden_todo_counts(['project'])
- end
- end
-
- def init_not_done_counts(parents = ['project','context'])
- parents.each do |parent|
- eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)")
- end
- end
-
- def init_project_hidden_todo_counts(parents = ['project','context'])
- parents.each do |parent|
- eval("@#{parent}_project_hidden_todo_counts = @#{parent}_project_hidden_todo_counts || current_user.todos.count(:conditions => ['state = ? or state = ?', 'project_hidden', 'active'], :group => :#{parent}_id)")
- end
- end
-
- # Set the contents of the flash message from a controller Usage: notify
- # :warning, "This is the message" Sets the flash of type 'warning' to "This is
- # the message"
- def notify(type, message)
- flash[type] = message
- logger.error("ERROR: #{message}") if type == :error
- end
-
- def set_time_zone
- Time.zone = current_user.prefs.time_zone if logged_in?
- end
-
-end
+# The filters added to this controller will be run for all controllers in the
+# application. Likewise will all the methods added be available for all
+# controllers.
+
+require_dependency "login_system"
+require_dependency "tracks/source_view"
+require "redcloth"
+
+require 'date'
+require 'time'
+
+# Commented the following line because of #744. It prevented rake db:migrate to
+# run because this tag went looking for the taggings table that did not exist
+# when you feshly create a new database Old comment: We need this in development
+# mode, or you get 'method missing' errors
+#
+# Tag
+
+class CannotAccessContext < RuntimeError; end
+
+class ApplicationController < ActionController::Base
+
+ protect_from_forgery :secret => SALT
+
+ helper :application
+ include LoginSystem
+ helper_method :current_user, :prefs
+
+ layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
+
+ before_filter :set_session_expiration
+ before_filter :set_time_zone
+ prepend_before_filter :login_required
+ prepend_before_filter :enable_mobile_content_negotiation
+ after_filter :set_charset
+
+
+
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::SanitizeHelper
+ extend ActionView::Helpers::SanitizeHelper::ClassMethods
+ helper_method :format_date, :markdown
+
+ # By default, sets the charset to UTF-8 if it isn't already set
+ def set_charset
+ headers["Content-Type"] ||= "text/html; charset=UTF-8"
+ end
+
+ def set_session_expiration
+ # http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
+ unless session == nil
+ return if @controller_name == 'feed' or session['noexpiry'] == "on"
+ # If the method is called by the feed controller (which we don't have
+ # under session control) or if we checked the box to keep logged in on
+ # login don't set the session expiry time.
+ if session
+ # Get expiry time (allow ten seconds window for the case where we have
+ # none)
+ expiry_time = session['expiry_time'] || Time.now + 10
+ if expiry_time < Time.now
+ # Too late, matey... bang goes your session!
+ reset_session
+ else
+ # Okay, you get another hour
+ session['expiry_time'] = Time.now + (60*60)
+ end
+ end
+ end
+ end
+
+ def render_failure message, status = 404
+ render :text => message, :status => status
+ end
+
+ # def rescue_action(exception)
+ # log_error(exception) if logger
+ # respond_to do |format|
+ # format.html do
+ # notify :warning, "An error occurred on the server."
+ # render :action => "index"
+ # end
+ # format.js { render :action => 'error' }
+ # format.xml { render :text => 'An error occurred on the server.' + $! }
+ # end
+ # end
+
+ # Returns a count of next actions in the given context or project The result
+ # is count and a string descriptor, correctly pluralised if there are no
+ # actions or multiple actions
+ #
+ def count_undone_todos_phrase(todos_parent, string="actions")
+ count = count_undone_todos(todos_parent)
+ if count == 1
+ word = string.singularize
+ else
+ word = string.pluralize
+ end
+ return count.to_s + " " + word
+ end
+
+ def count_undone_todos(todos_parent)
+ if todos_parent.nil?
+ count = 0
+ elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
+ count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
+ else
+ count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
+ end
+ count || 0
+ end
+
+ # Convert a date object to the format specified in the user's preferences in
+ # config/settings.yml
+ #
+ def format_date(date)
+ if date
+ date_format = prefs.date_format
+ formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}")
+ else
+ formatted_date = ''
+ end
+ formatted_date
+ end
+
+ # Uses RedCloth to transform text using either Textile or Markdown Need to
+ # require redcloth above RedCloth 3.0 or greater is needed to use Markdown,
+ # otherwise it only handles Textile
+ #
+ def markdown(text)
+ RedCloth.new(text).to_html
+ end
+
+ def build_default_project_context_name_map(projects)
+ Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
+ end
+
+ # Here's the concept behind this "mobile content negotiation" hack: In
+ # addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature
+ # 'mobile' version designed to be suitablef or use from a phone or PDA. It
+ # makes some sense that tne pages of that mobile version are simply alternate
+ # representations of the same Todo resources. The implementation goal was to
+ # treat mobile as another format and be able to use respond_to to render both
+ # versions. Unfortunately, I ran into a lot of trouble simply registering a
+ # new mime type 'text/html' with format :m because :html already is linked to
+ # that mime type and the new registration was forcing all html requests to be
+ # rendered in the mobile view. The before_filter and after_filter hackery
+ # below accomplishs that implementation goal by using a 'fake' mime type
+ # during the processing and then setting it to 'text/html' in an
+ # 'after_filter' -LKM 2007-04-01
+ def mobile?
+ return params[:format] == 'm'
+ end
+
+ def enable_mobile_content_negotiation
+ if mobile?
+ request.format = :m
+ end
+ end
+
+ def create_todo_from_recurring_todo(rt, date=nil)
+ # create todo and initialize with data from recurring_todo rt
+ todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
+
+ # set dates
+ todo.recurring_todo_id = rt.id
+ todo.due = rt.get_due_date(date)
+
+ show_from_date = rt.get_show_from_date(date)
+ if show_from_date.nil?
+ todo.show_from=nil
+ else
+ # make sure that show_from is not in the past
+ todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
+ end
+
+ saved = todo.save
+ if saved
+ todo.tag_with(rt.tag_list)
+ todo.tags.reload
+ end
+
+ # increate number of occurences created from recurring todo
+ rt.inc_occurences
+
+ # mark recurring todo complete if there are no next actions left
+ checkdate = todo.due.nil? ? todo.show_from : todo.due
+ rt.toggle_completion! unless rt.has_next_todo(checkdate)
+
+ return saved ? todo : nil
+ end
+
+ protected
+
+ def admin_login_required
+ unless User.find_by_id_and_is_admin(session['user_id'], true)
+ render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401
+ return false
+ end
+ end
+
+ def redirect_back_or_home
+ respond_to do |format|
+ format.html { redirect_back_or_default home_url }
+ format.m { redirect_back_or_default mobile_url }
+ end
+ end
+
+ def boolean_param(param_name)
+ return false if param_name.blank?
+ s = params[param_name]
+ return false if s.blank? || s == false || s =~ /^false$/i
+ return true if s == true || s =~ /^true$/i
+ raise ArgumentError.new("invalid value for Boolean: \"#{s}\"")
+ end
+
+ def self.openid_enabled?
+ Tracks::Config.openid_enabled?
+ end
+
+ def openid_enabled?
+ self.class.openid_enabled?
+ end
+
+ private
+
+ def parse_date_per_user_prefs( s )
+ prefs.parse_date(s)
+ end
+
+ def init_data_for_sidebar
+ @completed_projects = current_user.projects.completed
+ @hidden_projects = current_user.projects.hidden
+ @active_projects = current_user.projects.active
+
+ @active_contexts = current_user.contexts.active
+ @hidden_contexts = current_user.contexts.hidden
+
+ init_not_done_counts
+ if prefs.show_hidden_projects_in_sidebar
+ init_project_hidden_todo_counts(['project'])
+ end
+ end
+
+ def init_not_done_counts(parents = ['project','context'])
+ parents.each do |parent|
+ eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)")
+ end
+ end
+
+ def init_project_hidden_todo_counts(parents = ['project','context'])
+ parents.each do |parent|
+ eval("@#{parent}_project_hidden_todo_counts = @#{parent}_project_hidden_todo_counts || current_user.todos.count(:conditions => ['state = ? or state = ?', 'project_hidden', 'active'], :group => :#{parent}_id)")
+ end
+ end
+
+ # Set the contents of the flash message from a controller Usage: notify
+ # :warning, "This is the message" Sets the flash of type 'warning' to "This is
+ # the message"
+ def notify(type, message)
+ flash[type] = message
+ logger.error("ERROR: #{message}") if type == :error
+ end
+
+ def set_time_zone
+ Time.zone = current_user.prefs.time_zone if logged_in?
+ end
+
+end
diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb
index 668e6acb..aaed6a33 100644
--- a/app/controllers/recurring_todos_controller.rb
+++ b/app/controllers/recurring_todos_controller.rb
@@ -1,268 +1,268 @@
-class RecurringTodosController < ApplicationController
-
- helper :todos, :recurring_todos
-
- append_before_filter :init, :only => [:index, :new, :edit]
- append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update]
-
- def index
- find_and_inactivate
-
- @recurring_todos = current_user.recurring_todos.active
- @completed_recurring_todos = current_user.recurring_todos.completed
- @no_recurring_todos = @recurring_todos.size == 0
- @no_completed_recurring_todos = @completed_recurring_todos.size == 0
- @count = @recurring_todos.size
-
- @page_title = "TRACKS::Recurring Actions"
- end
-
- def new
- end
-
- def show
- end
-
- def edit
- respond_to do |format|
- format.js
- end
- end
-
- def update
- @recurring_todo.tag_with(params[:tag_list], current_user) if params[:tag_list]
- @original_item_context_id = @recurring_todo.context_id
- @original_item_project_id = @recurring_todo.project_id
-
- # we needed to rename the recurring_period selector in the edit form because
- # the form for a new recurring todo and the edit form are on the same page.
- # Same goes for start_from and end_date
- params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period']
- params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date'])
- params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from'])
-
- # update project
- if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil?
- if params['project_name'] == 'None'
- project = Project.null_object
- else
- project = current_user.projects.find_by_name(params['project_name'].strip)
- unless project
- project = current_user.projects.build
- project.name = params['project_name'].strip
- project.save
- @new_project_created = true
- end
- end
- params["recurring_todo"]["project_id"] = project.id
- end
-
- # update context
- if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank?
- context = current_user.contexts.find_by_name(params['context_name'].strip)
- unless context
- context = current_user.contexts.build
- context.name = params['context_name'].strip
- context.save
- @new_context_created = true
- end
- params["recurring_todo"]["context_id"] = context.id
- end
-
- params["recurring_todo"]["weekly_return_monday"]=' ' if params["recurring_todo"]["weekly_return_monday"].nil?
- params["recurring_todo"]["weekly_return_tuesday"]=' ' if params["recurring_todo"]["weekly_return_tuesday"].nil?
- params["recurring_todo"]["weekly_return_wednesday"]=' ' if params["recurring_todo"]["weekly_return_wednesday"].nil?
- params["recurring_todo"]["weekly_return_thursday"]=' ' if params["recurring_todo"]["weekly_return_thursday"].nil?
- params["recurring_todo"]["weekly_return_friday"]=' ' if params["recurring_todo"]["weekly_return_friday"].nil?
- params["recurring_todo"]["weekly_return_saturday"]=' ' if params["recurring_todo"]["weekly_return_saturday"].nil?
- params["recurring_todo"]["weekly_return_sunday"]=' ' if params["recurring_todo"]["weekly_return_sunday"].nil?
-
- @saved = @recurring_todo.update_attributes params["recurring_todo"]
-
- respond_to do |format|
- format.js
- end
- end
-
- def create
- p = RecurringTodoCreateParamsHelper.new(params)
- p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date'])
- p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from'])
-
- @recurring_todo = current_user.recurring_todos.build(p.selector_attributes)
- @recurring_todo.update_attributes(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?
- @recurring_todo.project_id = project.id
- 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?
- @recurring_todo.context_id = context.id
- end
-
- @recurring_saved = @recurring_todo.save
- unless (@recurring_saved == false) || p.tag_list.blank?
- @recurring_todo.tag_with(p.tag_list, current_user)
- @recurring_todo.tags.reload
- end
-
- if @recurring_saved
- @message = "The recurring todo was saved"
- @todo_saved = create_todo_from_recurring_todo(@recurring_todo).nil? == false
- if @todo_saved
- @message += " / created a new todo"
- else
- @message += " / did not create todo"
- end
- @count = current_user.recurring_todos.active.count
- else
- @message = "Error saving recurring todo"
- end
-
- respond_to do |format|
- format.js
- end
- end
-
- def destroy
-
- # remove all references to this recurring todo
- @todos = @recurring_todo.todos
- @number_of_todos = @todos.size
- @todos.each do |t|
- t.recurring_todo_id = nil
- t.save
- end
-
- # delete the recurring todo
- @saved = @recurring_todo.destroy
- @remaining = current_user.recurring_todos.count
-
- respond_to do |format|
-
- format.html do
- if @saved
- notify :notice, "Successfully deleted recurring action", 2.0
- redirect_to :action => 'index'
- else
- notify :error, "Failed to delete the recurring action", 2.0
- redirect_to :action => 'index'
- end
- end
-
- format.js do
- render
- end
- end
- end
-
- def toggle_check
- @saved = @recurring_todo.toggle_completion!
-
- @count = current_user.recurring_todos.active.count
- @remaining = @count
-
- if @recurring_todo.active?
- @remaining = current_user.recurring_todos.completed.count
-
- # from completed back to active -> check if there is an active todo
- # current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]})
- @active_todos = @recurring_todo.todos.active.count
- # create todo if there is no active todo belonging to the activated
- # recurring_todo
- @new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0
- end
-
- respond_to do |format|
- format.js
- end
- end
-
- def toggle_star
- @recurring_todo.toggle_star!
- @saved = @recurring_todo.save!
- respond_to do |format|
- format.js
- end
- end
-
- class RecurringTodoCreateParamsHelper
-
- def initialize(params)
- @params = params['request'] || params
- @attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo']
-
- # make sure all selectors (recurring_period, recurrence_selector,
- # daily_selector, monthly_selector and yearly_selector) are first in hash
- # so that they are processed first by the model
- @selector_attributes = {
- 'recurring_period' => @attributes['recurring_period'],
- 'daily_selector' => @attributes['daily_selector'],
- 'monthly_selector' => @attributes['monthly_selector'],
- 'yearly_selector' => @attributes['yearly_selector']
- }
- end
-
- def attributes
- @attributes
- end
-
- def selector_attributes
- return @selector_attributes
- end
-
- def project_name
- @params['project_name'].strip unless @params['project_name'].nil?
- end
-
- def context_name
- @params['context_name'].strip unless @params['context_name'].nil?
- end
-
- def tag_list
- @params['tag_list']
- end
-
- def project_specified_by_name?
- return false unless @attributes['project_id'].blank?
- return false if project_name.blank?
- return false if project_name == 'None'
- true
- end
-
- def context_specified_by_name?
- return false unless @attributes['context_id'].blank?
- return false if context_name.blank?
- true
- end
-
- end
-
- private
-
- def init
- @days_of_week = [ ['Sunday',0], ['Monday',1], ['Tuesday', 2], ['Wednesday',3], ['Thursday',4], ['Friday',5], ['Saturday',6]]
- @months_of_year = [
- ['January',1], ['Februari',2], ['March', 3], ['April',4], ['May',5], ['June',6],
- ['July',7], ['August',8], ['September',9], ['October', 10], ['November', 11], ['December',12]]
- @xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]]
- @projects = current_user.projects.find(:all, :include => [:default_context])
- @contexts = current_user.contexts.find(:all)
- @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
- end
-
- def get_recurring_todo_from_param
- @recurring_todo = current_user.recurring_todos.find(params[:id])
- end
-
- def find_and_inactivate
- # find active recurring todos without active todos and inactivate them
- recurring_todos = current_user.recurring_todos.active
- recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0}
- end
-
-end
+class RecurringTodosController < ApplicationController
+
+ helper :todos, :recurring_todos
+
+ append_before_filter :init, :only => [:index, :new, :edit]
+ append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update]
+
+ def index
+ find_and_inactivate
+
+ @recurring_todos = current_user.recurring_todos.active
+ @completed_recurring_todos = current_user.recurring_todos.completed
+ @no_recurring_todos = @recurring_todos.size == 0
+ @no_completed_recurring_todos = @completed_recurring_todos.size == 0
+ @count = @recurring_todos.size
+
+ @page_title = "TRACKS::Recurring Actions"
+ end
+
+ def new
+ end
+
+ def show
+ end
+
+ def edit
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def update
+ @recurring_todo.tag_with(params[:tag_list]) if params[:tag_list]
+ @original_item_context_id = @recurring_todo.context_id
+ @original_item_project_id = @recurring_todo.project_id
+
+ # we needed to rename the recurring_period selector in the edit form because
+ # the form for a new recurring todo and the edit form are on the same page.
+ # Same goes for start_from and end_date
+ params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period']
+ params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date'])
+ params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from'])
+
+ # update project
+ if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil?
+ if params['project_name'] == 'None'
+ project = Project.null_object
+ else
+ project = current_user.projects.find_by_name(params['project_name'].strip)
+ unless project
+ project = current_user.projects.build
+ project.name = params['project_name'].strip
+ project.save
+ @new_project_created = true
+ end
+ end
+ params["recurring_todo"]["project_id"] = project.id
+ end
+
+ # update context
+ if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank?
+ context = current_user.contexts.find_by_name(params['context_name'].strip)
+ unless context
+ context = current_user.contexts.build
+ context.name = params['context_name'].strip
+ context.save
+ @new_context_created = true
+ end
+ params["recurring_todo"]["context_id"] = context.id
+ end
+
+ params["recurring_todo"]["weekly_return_monday"]=' ' if params["recurring_todo"]["weekly_return_monday"].nil?
+ params["recurring_todo"]["weekly_return_tuesday"]=' ' if params["recurring_todo"]["weekly_return_tuesday"].nil?
+ params["recurring_todo"]["weekly_return_wednesday"]=' ' if params["recurring_todo"]["weekly_return_wednesday"].nil?
+ params["recurring_todo"]["weekly_return_thursday"]=' ' if params["recurring_todo"]["weekly_return_thursday"].nil?
+ params["recurring_todo"]["weekly_return_friday"]=' ' if params["recurring_todo"]["weekly_return_friday"].nil?
+ params["recurring_todo"]["weekly_return_saturday"]=' ' if params["recurring_todo"]["weekly_return_saturday"].nil?
+ params["recurring_todo"]["weekly_return_sunday"]=' ' if params["recurring_todo"]["weekly_return_sunday"].nil?
+
+ @saved = @recurring_todo.update_attributes params["recurring_todo"]
+
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def create
+ p = RecurringTodoCreateParamsHelper.new(params)
+ p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date'])
+ p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from'])
+
+ @recurring_todo = current_user.recurring_todos.build(p.selector_attributes)
+ @recurring_todo.update_attributes(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?
+ @recurring_todo.project_id = project.id
+ 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?
+ @recurring_todo.context_id = context.id
+ end
+
+ @recurring_saved = @recurring_todo.save
+ unless (@recurring_saved == false) || p.tag_list.blank?
+ @recurring_todo.tag_with(p.tag_list)
+ @recurring_todo.tags.reload
+ end
+
+ if @recurring_saved
+ @message = "The recurring todo was saved"
+ @todo_saved = create_todo_from_recurring_todo(@recurring_todo).nil? == false
+ if @todo_saved
+ @message += " / created a new todo"
+ else
+ @message += " / did not create todo"
+ end
+ @count = current_user.recurring_todos.active.count
+ else
+ @message = "Error saving recurring todo"
+ end
+
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def destroy
+
+ # remove all references to this recurring todo
+ @todos = @recurring_todo.todos
+ @number_of_todos = @todos.size
+ @todos.each do |t|
+ t.recurring_todo_id = nil
+ t.save
+ end
+
+ # delete the recurring todo
+ @saved = @recurring_todo.destroy
+ @remaining = current_user.recurring_todos.count
+
+ respond_to do |format|
+
+ format.html do
+ if @saved
+ notify :notice, "Successfully deleted recurring action", 2.0
+ redirect_to :action => 'index'
+ else
+ notify :error, "Failed to delete the recurring action", 2.0
+ redirect_to :action => 'index'
+ end
+ end
+
+ format.js do
+ render
+ end
+ end
+ end
+
+ def toggle_check
+ @saved = @recurring_todo.toggle_completion!
+
+ @count = current_user.recurring_todos.active.count
+ @remaining = @count
+
+ if @recurring_todo.active?
+ @remaining = current_user.recurring_todos.completed.count
+
+ # from completed back to active -> check if there is an active todo
+ # current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]})
+ @active_todos = @recurring_todo.todos.active.count
+ # create todo if there is no active todo belonging to the activated
+ # recurring_todo
+ @new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0
+ end
+
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def toggle_star
+ @recurring_todo.toggle_star!
+ @saved = @recurring_todo.save!
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ class RecurringTodoCreateParamsHelper
+
+ def initialize(params)
+ @params = params['request'] || params
+ @attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo']
+
+ # make sure all selectors (recurring_period, recurrence_selector,
+ # daily_selector, monthly_selector and yearly_selector) are first in hash
+ # so that they are processed first by the model
+ @selector_attributes = {
+ 'recurring_period' => @attributes['recurring_period'],
+ 'daily_selector' => @attributes['daily_selector'],
+ 'monthly_selector' => @attributes['monthly_selector'],
+ 'yearly_selector' => @attributes['yearly_selector']
+ }
+ end
+
+ def attributes
+ @attributes
+ end
+
+ def selector_attributes
+ return @selector_attributes
+ end
+
+ def project_name
+ @params['project_name'].strip unless @params['project_name'].nil?
+ end
+
+ def context_name
+ @params['context_name'].strip unless @params['context_name'].nil?
+ end
+
+ def tag_list
+ @params['tag_list']
+ end
+
+ def project_specified_by_name?
+ return false unless @attributes['project_id'].blank?
+ return false if project_name.blank?
+ return false if project_name == 'None'
+ true
+ end
+
+ def context_specified_by_name?
+ return false unless @attributes['context_id'].blank?
+ return false if context_name.blank?
+ true
+ end
+
+ end
+
+ private
+
+ def init
+ @days_of_week = [ ['Sunday',0], ['Monday',1], ['Tuesday', 2], ['Wednesday',3], ['Thursday',4], ['Friday',5], ['Saturday',6]]
+ @months_of_year = [
+ ['January',1], ['Februari',2], ['March', 3], ['April',4], ['May',5], ['June',6],
+ ['July',7], ['August',8], ['September',9], ['October', 10], ['November', 11], ['December',12]]
+ @xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]]
+ @projects = current_user.projects.find(:all, :include => [:default_context])
+ @contexts = current_user.contexts.find(:all)
+ @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
+ end
+
+ def get_recurring_todo_from_param
+ @recurring_todo = current_user.recurring_todos.find(params[:id])
+ end
+
+ def find_and_inactivate
+ # find active recurring todos without active todos and inactivate them
+ recurring_todos = current_user.recurring_todos.active
+ recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0}
+ end
+
+end
diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb
index 8ccf710a..ff5b767f 100644
--- a/app/controllers/todos_controller.rb
+++ b/app/controllers/todos_controller.rb
@@ -1,932 +1,932 @@
-class TodosController < ApplicationController
-
- helper :todos
-
- skip_before_filter :login_required, :only => [:index, :calendar]
- prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar]
- append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create, :calendar ]
- append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy ]
-
- session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
-
- def index
- current_user.deferred_todos.find_and_activate_ready
- @projects = current_user.projects.find(:all, :include => [:default_context])
- @contexts = current_user.contexts.find(:all)
-
- @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( :except => :user_id ) }
- 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.find(:all)
- respond_to do |format|
- format.m {
- @new_mobile = true
- @return_path=cookies[:mobile_url]
- @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'
- p = TodoCreateParamsHelper.new(params, prefs)
- p.parse_dates() unless mobile?
-
- @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
- 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
- end
-
- @saved = @todo.save
- unless (@saved == false) || p.tag_list.blank?
- @todo.tag_with(p.tag_list, current_user)
- @todo.tags.reload
- end
-
- respond_to do |format|
- format.html { redirect_to :action => "index" }
- format.m do
- @return_path=cookies[:mobile_url]
- # todo: use function for this fixed path
- @return_path='/m' if @return_path.nil?
- if @saved
- redirect_to @return_path
- else
- @projects = current_user.projects.find(:all)
- @contexts = current_user.contexts.find(:all)
- render :action => "new"
- end
- end
- format.js do
- determine_down_count if @saved
- @contexts = current_user.contexts.find(:all) if @new_context_created
- @projects = current_user.projects.find(:all) if @new_project_created
- @initial_context_name = params['default_context_name']
- @initial_project_name = params['default_project_name']
- 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
-
- def edit
- @projects = current_user.projects.find(:all)
- @contexts = current_user.contexts.find(:all)
- @source_view = params['_source_view'] || 'todo'
- @tag_name = params['_tag_name']
- respond_to do |format|
- format.js
- end
- end
-
- def show
- respond_to do |format|
- format.m do
- @projects = current_user.projects.active
- @contexts = current_user.contexts.find(:all)
- @edit_mobile = true
- @return_path=cookies[:mobile_url]
- render :action => 'show'
- end
- format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
- end
- end
-
- # Toggles the 'done' status of the action
- #
- def toggle_check
- @source_view = params['_source_view'] || 'todo'
- @original_item_due = @todo.due
- @saved = @todo.toggle_completion!
-
- # 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.js do
- if @saved
- determine_remaining_in_context_count(@todo.context_id)
- determine_down_count
- determine_completed_count if @todo.completed?
- 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( :except => :user_id ) }
- format.html do
- if @saved
- # TODO: I think this will work, but can't figure out how to test it
- notify :notice, "The action '#{@todo.description}' was marked as #{@todo.completed? ? 'complete' : 'incomplete' }"
- redirect_to :action => "index"
- else
- notify :notice, "The action '#{@todo.description}' was NOT marked as #{@todo.completed? ? 'complete' : 'incomplete' } due to an error on the server.", "index"
- redirect_to :action => "index"
- end
- end
- end
- end
-
- def toggle_star
- @todo.toggle_star!
- @saved = @todo.save!
- respond_to do |format|
- format.js
- format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
- end
- end
-
- def update
- @source_view = params['_source_view'] || 'todo'
- init_data_for_sidebar unless mobile?
- @todo.tag_with(params[:tag_list], current_user) if params[:tag_list]
- @original_item_context_id = @todo.context_id
- @original_item_project_id = @todo.project_id
- @original_item_was_deferred = @todo.deferred?
- @original_item_due = @todo.due
- @original_item_due_id = get_due_id_for_calendar(@todo.due)
-
- if params['todo']['project_id'].blank? && !params['project_name'].nil?
- if params['project_name'] == 'None'
- project = Project.null_object
- else
- project = current_user.projects.find_by_name(params['project_name'].strip)
- unless project
- project = current_user.projects.build
- project.name = params['project_name'].strip
- project.save
- @new_project_created = true
- end
- end
- params["todo"]["project_id"] = project.id
- end
-
- if params['todo']['context_id'].blank? && !params['context_name'].blank?
- context = current_user.contexts.find_by_name(params['context_name'].strip)
- unless context
- context = current_user.contexts.build
- context.name = params['context_name'].strip
- context.save
- @new_context_created = true
- @not_done_todos = [@todo]
- end
- params["todo"]["context_id"] = context.id
- end
-
- if params["todo"].has_key?("due")
- params["todo"]["due"] = parse_date_per_user_prefs(params["todo"]["due"])
- else
- params["todo"]["due"] = ""
- end
-
- if params['todo']['show_from']
- params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from'])
- end
-
- if params['done'] == '1' && !@todo.completed?
- @todo.complete!
- 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!
- end
-
- @saved = @todo.update_attributes params["todo"]
- @context_changed = @original_item_context_id != @todo.context_id
- @todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
-
- if source_view_is :calendar
- @due_date_changed = @original_item_due != @todo.due
- if @due_date_changed
- @old_due_empty = is_old_due_empty(@original_item_due_id)
- if @todo.due.nil?
- # do not act further on date change when date is changed to nil
- @due_date_changed = false
- else
- @new_due_id = get_due_id_for_calendar(@todo.due)
- end
- end
- end
-
- if @context_changed
- determine_remaining_in_context_count(@original_item_context_id)
- else
- determine_remaining_in_context_count(@todo.context_id)
- end
-
- @project_changed = @original_item_project_id != @todo.project_id
- if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = current_user.projects.find(@original_item_project_id).not_done_todo_count; end
- determine_down_count
- respond_to do |format|
- format.js
- format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
- format.m do
- if @saved
- if cookies[:mobile_url]
- cookies[:mobile_url] = {:value => nil, :secure => TRACKS_COOKIES_SECURE}
- redirect_to cookies[:mobile_url]
- else
- redirect_to formatted_todos_path(:m)
- end
- else
- render :action => "edit", :format => :m
- end
- end
- end
- end
-
- def destroy
- @todo = get_todo_from_params
- @original_item_due = @todo.due
- @context_id = @todo.context_id
- @project_id = @todo.project_id
-
- @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
- notify :notice, "Successfully deleted next action", 2.0
- redirect_to :action => 'index'
- else
- notify :error, "Failed to delete the action", 2.0
- redirect_to :action => 'index'
- end
- end
-
- format.js do
- if @saved
- determine_down_count
- if source_view_is_one_of(:todo, :deferred)
- 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 completed
- @page_title = "TRACKS::Completed tasks"
- @done = current_user.completed_todos
- @done_today = @done.completed_within Time.zone.now - 1.day
- @done_this_week = @done.completed_within Time.zone.now - 1.week
- @done_this_month = @done.completed_within Time.zone.now - 4.week
- @count = @done_today.size + @done_this_week.size + @done_this_month.size
- end
-
- def completed_archive
- @page_title = "TRACKS::Archived completed tasks"
- @done = current_user.completed_todos
- @count = @done.size
- @done_archive = @done.completed_more_than Time.zone.now - 28.days
- end
-
- def list_deferred
- @source_view = 'deferred'
- @page_title = "TRACKS::Tickler"
-
- @projects = current_user.projects.find(:all, :include => [ :todos, :default_context ])
- @contexts_to_show = @contexts = current_user.contexts.find(:all, :include => [ :todos ])
-
- current_user.deferred_todos.find_and_activate_ready
- @not_done_todos = current_user.deferred_todos
- @count = @not_done_todos.size
- @down_count = @count
- @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json unless mobile?
-
- respond_to do |format|
- format.html
- format.m { render :action => 'mobile_list_deferred' }
- 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 formatted_context_todos_path(context, :m)
- end
-
- def filter_to_project
- project = current_user.projects.find(params['project']['id'])
- redirect_to formatted_project_todos_path(project, :m)
- end
-
- # /todos/tag/[tag_name] shows all the actions tagged with tag_name
- def tag
- @source_view = params['_source_view'] || 'tag'
- @tag_name = params[:name]
- @page_title = "TRACKS::Tagged with \'#{@tag_name}\'"
-
- # mobile tags are routed with :name ending on .m. So we need to chomp it
- @tag_name = @tag_name.chomp('.m') if mobile?
-
- @tag = Tag.find_by_name(@tag_name)
- @tag = Tag.new(:name => @tag_name) if @tag.nil?
- tag_collection = @tag.todos
-
- @not_done_todos = tag_collection.find(:all,
- :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'],
- :order => 'todos.due IS NULL, todos.due ASC, todos.created_at ASC')
- @hidden_todos = current_user.todos.find(:all,
- :include => [:taggings, :tags, :context],
- :conditions => ['tags.name = ? AND (todos.state = ? OR (contexts.hide = ? AND todos.state = ?))', @tag_name, 'project_hidden', true, 'active'],
- :order => 'todos.completed_at DESC, todos.created_at DESC')
- @deferred = tag_collection.find(:all,
- :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'deferred'],
- :order => 'show_from ASC, todos.created_at DESC')
-
- # If you've set no_completed to zero, the completed items box isn't shown on
- # the tag page
- max_completed = current_user.prefs.show_number_completed
- @done = tag_collection.find(:all,
- :limit => max_completed,
- :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'completed'],
- :order => 'todos.completed_at DESC')
-
- @projects = current_user.projects
- @contexts = current_user.contexts
- @contexts_to_show = @contexts.reject {|x| x.hide? }
-
- # 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 {
- @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
- }
- format.m {
- cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
- render :action => "mobile_tag"
- }
- end
- end
-
- def defer
- @source_view = params['_source_view'] || 'todo'
- numdays = params['days'].to_i
- @todo = Todo.find(params[:id])
- @todo.show_from = (@todo.show_from || @todo.user.date) + numdays.days
- @saved = @todo.save
-
- determine_down_count
- determine_remaining_in_context_count(@todo.context_id)
- respond_to do |format|
- format.html { redirect_to :back }
- format.js {render :action => 'update'}
- end
- end
-
- def calendar
- @source_view = params['_source_view'] || 'calendar'
- @page_title = "TRACKS::Calendar"
-
- 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
-
- @due_today = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due <= ?', due_today_date],
- :order => "due")
- @due_this_week = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date],
- :order => "due")
- @due_next_week = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date],
- :order => "due")
- @due_this_month = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date],
- :order => "due")
- @due_after_this_month = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due > ?', due_this_month_date],
- :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.find(:all, :order => "due")
- render :action => 'calendar', :layout => false, :content_type => Mime::ICS
- }
- end
- end
-
- private
-
- def get_todo_from_params
- @todo = current_user.todos.find(params['id'])
- 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(:with_scope, :find => {:conditions => ['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 = "Tracks - Next Actions"
- @description = "Filter: "
-
- 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 << " due today" if (due_within == 0)
- @title << " due within a week" if (due_within == 6)
- @description << " with a due date #{due_within_date_s} or earlier"
- 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 << " actions completed"
- @description << " in the last #{done_in_last.to_s} days"
- 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 :with_scope, :find => {:conditions => condition_builder.to_conditions} do
- yield
- end
-
- end
-
- def with_parent_resource_scope(&block)
- @feed_title = "Actions "
- if (params[:context_id])
- @context = current_user.contexts.find_by_params(params)
- @feed_title = @feed_title + "in context '#{@context.name}'"
- Todo.send :with_scope, :find => {:conditions => ['todos.context_id = ?', @context.id]} do
- yield
- end
- elsif (params[:project_id])
- @project = current_user.projects.find_by_params(params)
- @feed_title = @feed_title + "in project '#{@project.name}'"
- @project_feed = true
- Todo.send :with_scope, :find => {:conditions => ['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 << "Lists the last #{params['limit']} incomplete next actions"
- else
- @description << "Lists 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 = Todo.find(:all, :conditions => ['todos.user_id = ?', current_user.id], :include => [ :project, :context, :tags ])
-
- # Exclude hidden projects from the home page
- @not_done_todos = Todo.find(:all,
- :conditions => ['todos.user_id = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)',
- current_user.id, false, 'active'],
- :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
- :include => [ :project, :context, :tags ])
- 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 = Todo.find(:all,
- :conditions => ['todos.user_id = ? AND todos.state = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)',
- current_user.id, 'active', false, 'active'],
- :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
- :include => [ :project, :context, :tags ])
- end
-
- def determine_down_count
- source_view do |from|
- from.todo do
- @down_count = Todo.count(
- :all,
- :conditions => ['todos.user_id = ? and todos.state = ? and contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', current_user.id, 'active', false, 'active'],
- :include => [ :project, :context ])
- # #@down_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos,
- # contexts WHERE todos.context_id = contexts.id and todos.user_id = ?
- # and todos.state = ? and contexts.hide = ?', current_user.id, 'active',
- # false])
- end
- from.context do
- @down_count = current_user.contexts.find(@todo.context_id).not_done_todo_count
- end
- from.project do
- unless @todo.project_id == nil
- @down_count = current_user.projects.find(@todo.project_id).not_done_todo_count(:include_project_hidden_todos => true)
- @deferred_count = current_user.projects.find(@todo.project_id).deferred_todo_count
- end
- end
- from.deferred do
- @down_count = current_user.todos.count_in_state(:deferred)
- 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
- tag_collection = @tag.todos
- @not_done_todos = tag_collection.find(:all, :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'])
- @not_done_todos.empty? ? @down_count = 0 : @down_count = @not_done_todos.size
- end
- end
- end
-
- def determine_remaining_in_context_count(context_id = @todo.context_id)
- source_view do |from|
- from.deferred { @remaining_in_context = current_user.contexts.find(context_id).deferred_todo_count }
- from.tag {
- tag = Tag.find_by_name(params['_tag_name'])
- if tag.nil?
- tag = Tag.new(:name => params['tag'])
- end
- @remaining_in_context = current_user.contexts.find(context_id).not_done_todo_count({:tag => tag.id})
- }
- end
- @remaining_in_context = current_user.contexts.find(context_id).not_done_todo_count if @remaining_in_context.nil?
- end
-
- def determine_completed_count
- source_view do |from|
- from.todo do
- @completed_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos, contexts WHERE todos.context_id = contexts.id and todos.user_id = ? and todos.state = ? and contexts.hide = ?', current_user.id, 'completed', false])
- end
- from.context do
- @completed_count = current_user.contexts.find(@todo.context_id).done_todo_count
- end
- from.project do
- unless @todo.project_id == nil
- @completed_count = current_user.projects.find(@todo.project_id).done_todo_count
- end
- end
- end
- end
-
- def render_todos_html
- lambda do
- @page_title = "TRACKS::List tasks"
-
- # 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.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
-
- # Set count badge to number of not-done, not hidden context items
- @count = 0
- @todos.each do |x|
- if x.active?
- if x.project.nil?
- @count += 1 if !x.context.hide?
- else
- @count += 1 if x.project.active? && !x.context.hide?
- end
- end
- end
-
- @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
-
- render
- end
- end
-
- def render_todos_mobile
- lambda do
- @page_title = "All actions"
- @home = true
- cookies[:mobile_url]= { :value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
- 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
- lambda do |i|
- item_notes = sanitize(markdown( i.notes )) if i.notes?
- due = "
Due: #{format_date(i.due)}
\n" if i.due?
- done = "Completed: #{format_date(i.completed_at)}
\n" if i.completed?
- context_link = "#{ i.context.name }"
- if i.project_id?
- project_link = "#{ i.project.name }"
- else
- project_link = "none"
- end
- "#{done||''}#{due||''}#{item_notes||''}\nProject: #{project_link}
\nContext: #{context_link}
"
- end
- end
-
- def render_atom_feed
- lambda do
- render_atom_feed_for @todos, :feed => todo_feed_options,
- :item => {
- :title => :description,
- :link => lambda { |t| context_url(t.context) },
- :description => todo_feed_content,
- :author => lambda { |p| nil }
- }
- end
- end
-
- def render_text_feed
- lambda do
- render :action => 'index', :layout => false, :content_type => Mime::TEXT
- end
- end
-
- def render_ical_feed
- lambda do
- render :action => 'index', :layout => false, :content_type => Mime::ICS
- end
- end
-
- def self.is_feed_request(req)
- ['rss','atom','txt','ics'].include?(req.parameters[:format])
- 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.has_next_todo(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 = create_todo_from_recurring_todo(recurring_todo, 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)
- 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 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due <= ?', due_today_date])
- when "due_this_week"
- return 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date])
- when "due_next_week"
- return 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date])
- when "due_this_month"
- return 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date])
- when "due_after_this_month"
- return 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due > ?', due_this_month_date])
- else
- raise Exception.new, "unknown due id for calendar: '#{id}'"
- end
- end
-
- class FindConditionBuilder
-
- def initialize
- @queries = Array.new
- @params = Array.new
- end
-
- def add(query, param)
- @queries << query
- @params << param
- end
-
- def to_conditions
- [@queries.join(' AND ')] + @params
- end
- end
-
- class TodoCreateParamsHelper
-
- def initialize(params, prefs)
- @params = params['request'] || params
- @prefs = prefs
- @attributes = params['request'] && params['request']['todo'] || params['todo']
- end
-
- def attributes
- @attributes
- end
-
- def show_from
- @attributes['show_from']
- end
-
- def due
- @attributes['due']
- end
-
- def project_name
- @params['project_name'].strip unless @params['project_name'].nil?
- end
-
- def context_name
- @params['context_name'].strip unless @params['context_name'].nil?
- end
-
- def tag_list
- @params['tag_list']
- end
-
- def parse_dates()
- @attributes['show_from'] = @prefs.parse_date(show_from)
- @attributes['due'] = @prefs.parse_date(due)
- @attributes['due'] ||= ''
- end
-
- def project_specified_by_name?
- return false unless @attributes['project_id'].blank?
- return false if project_name.blank?
- return false if project_name == 'None'
- true
- end
-
- def context_specified_by_name?
- return false unless @attributes['context_id'].blank?
- return false if context_name.blank?
- true
- end
-
- end
-end
+class TodosController < ApplicationController
+
+ helper :todos
+
+ skip_before_filter :login_required, :only => [:index, :calendar]
+ prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar]
+ append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create, :calendar ]
+ append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy ]
+
+ session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
+
+ def index
+ current_user.deferred_todos.find_and_activate_ready
+ @projects = current_user.projects.find(:all, :include => [:default_context])
+ @contexts = current_user.contexts.find(:all)
+
+ @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( :except => :user_id ) }
+ 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.find(:all)
+ respond_to do |format|
+ format.m {
+ @new_mobile = true
+ @return_path=cookies[:mobile_url]
+ @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'
+ p = TodoCreateParamsHelper.new(params, prefs)
+ p.parse_dates() unless mobile?
+
+ @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
+ 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
+ end
+
+ @saved = @todo.save
+ unless (@saved == false) || p.tag_list.blank?
+ @todo.tag_with(p.tag_list)
+ @todo.tags.reload
+ end
+
+ respond_to do |format|
+ format.html { redirect_to :action => "index" }
+ format.m do
+ @return_path=cookies[:mobile_url]
+ # todo: use function for this fixed path
+ @return_path='/m' if @return_path.nil?
+ if @saved
+ redirect_to @return_path
+ else
+ @projects = current_user.projects.find(:all)
+ @contexts = current_user.contexts.find(:all)
+ render :action => "new"
+ end
+ end
+ format.js do
+ determine_down_count if @saved
+ @contexts = current_user.contexts.find(:all) if @new_context_created
+ @projects = current_user.projects.find(:all) if @new_project_created
+ @initial_context_name = params['default_context_name']
+ @initial_project_name = params['default_project_name']
+ 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
+
+ def edit
+ @projects = current_user.projects.find(:all)
+ @contexts = current_user.contexts.find(:all)
+ @source_view = params['_source_view'] || 'todo'
+ @tag_name = params['_tag_name']
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def show
+ respond_to do |format|
+ format.m do
+ @projects = current_user.projects.active
+ @contexts = current_user.contexts.find(:all)
+ @edit_mobile = true
+ @return_path=cookies[:mobile_url]
+ render :action => 'show'
+ end
+ format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
+ end
+ end
+
+ # Toggles the 'done' status of the action
+ #
+ def toggle_check
+ @source_view = params['_source_view'] || 'todo'
+ @original_item_due = @todo.due
+ @saved = @todo.toggle_completion!
+
+ # 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.js do
+ if @saved
+ determine_remaining_in_context_count(@todo.context_id)
+ determine_down_count
+ determine_completed_count if @todo.completed?
+ 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( :except => :user_id ) }
+ format.html do
+ if @saved
+ # TODO: I think this will work, but can't figure out how to test it
+ notify :notice, "The action '#{@todo.description}' was marked as #{@todo.completed? ? 'complete' : 'incomplete' }"
+ redirect_to :action => "index"
+ else
+ notify :notice, "The action '#{@todo.description}' was NOT marked as #{@todo.completed? ? 'complete' : 'incomplete' } due to an error on the server.", "index"
+ redirect_to :action => "index"
+ end
+ end
+ end
+ end
+
+ def toggle_star
+ @todo.toggle_star!
+ @saved = @todo.save!
+ respond_to do |format|
+ format.js
+ format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
+ end
+ end
+
+ def update
+ @source_view = params['_source_view'] || 'todo'
+ init_data_for_sidebar unless mobile?
+ @todo.tag_with(params[:tag_list]) if params[:tag_list]
+ @original_item_context_id = @todo.context_id
+ @original_item_project_id = @todo.project_id
+ @original_item_was_deferred = @todo.deferred?
+ @original_item_due = @todo.due
+ @original_item_due_id = get_due_id_for_calendar(@todo.due)
+
+ if params['todo']['project_id'].blank? && !params['project_name'].nil?
+ if params['project_name'] == 'None'
+ project = Project.null_object
+ else
+ project = current_user.projects.find_by_name(params['project_name'].strip)
+ unless project
+ project = current_user.projects.build
+ project.name = params['project_name'].strip
+ project.save
+ @new_project_created = true
+ end
+ end
+ params["todo"]["project_id"] = project.id
+ end
+
+ if params['todo']['context_id'].blank? && !params['context_name'].blank?
+ context = current_user.contexts.find_by_name(params['context_name'].strip)
+ unless context
+ context = current_user.contexts.build
+ context.name = params['context_name'].strip
+ context.save
+ @new_context_created = true
+ @not_done_todos = [@todo]
+ end
+ params["todo"]["context_id"] = context.id
+ end
+
+ if params["todo"].has_key?("due")
+ params["todo"]["due"] = parse_date_per_user_prefs(params["todo"]["due"])
+ else
+ params["todo"]["due"] = ""
+ end
+
+ if params['todo']['show_from']
+ params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from'])
+ end
+
+ if params['done'] == '1' && !@todo.completed?
+ @todo.complete!
+ 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!
+ end
+
+ @saved = @todo.update_attributes params["todo"]
+ @context_changed = @original_item_context_id != @todo.context_id
+ @todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
+
+ if source_view_is :calendar
+ @due_date_changed = @original_item_due != @todo.due
+ if @due_date_changed
+ @old_due_empty = is_old_due_empty(@original_item_due_id)
+ if @todo.due.nil?
+ # do not act further on date change when date is changed to nil
+ @due_date_changed = false
+ else
+ @new_due_id = get_due_id_for_calendar(@todo.due)
+ end
+ end
+ end
+
+ if @context_changed
+ determine_remaining_in_context_count(@original_item_context_id)
+ else
+ determine_remaining_in_context_count(@todo.context_id)
+ end
+
+ @project_changed = @original_item_project_id != @todo.project_id
+ if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = current_user.projects.find(@original_item_project_id).not_done_todo_count; end
+ determine_down_count
+ respond_to do |format|
+ format.js
+ format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
+ format.m do
+ if @saved
+ if cookies[:mobile_url]
+ cookies[:mobile_url] = {:value => nil, :secure => TRACKS_COOKIES_SECURE}
+ redirect_to cookies[:mobile_url]
+ else
+ redirect_to formatted_todos_path(:m)
+ end
+ else
+ render :action => "edit", :format => :m
+ end
+ end
+ end
+ end
+
+ def destroy
+ @todo = get_todo_from_params
+ @original_item_due = @todo.due
+ @context_id = @todo.context_id
+ @project_id = @todo.project_id
+
+ @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
+ notify :notice, "Successfully deleted next action", 2.0
+ redirect_to :action => 'index'
+ else
+ notify :error, "Failed to delete the action", 2.0
+ redirect_to :action => 'index'
+ end
+ end
+
+ format.js do
+ if @saved
+ determine_down_count
+ if source_view_is_one_of(:todo, :deferred)
+ 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 completed
+ @page_title = "TRACKS::Completed tasks"
+ @done = current_user.completed_todos
+ @done_today = @done.completed_within Time.zone.now - 1.day
+ @done_this_week = @done.completed_within Time.zone.now - 1.week
+ @done_this_month = @done.completed_within Time.zone.now - 4.week
+ @count = @done_today.size + @done_this_week.size + @done_this_month.size
+ end
+
+ def completed_archive
+ @page_title = "TRACKS::Archived completed tasks"
+ @done = current_user.completed_todos
+ @count = @done.size
+ @done_archive = @done.completed_more_than Time.zone.now - 28.days
+ end
+
+ def list_deferred
+ @source_view = 'deferred'
+ @page_title = "TRACKS::Tickler"
+
+ @projects = current_user.projects.find(:all, :include => [ :todos, :default_context ])
+ @contexts_to_show = @contexts = current_user.contexts.find(:all, :include => [ :todos ])
+
+ current_user.deferred_todos.find_and_activate_ready
+ @not_done_todos = current_user.deferred_todos
+ @count = @not_done_todos.size
+ @down_count = @count
+ @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json unless mobile?
+
+ respond_to do |format|
+ format.html
+ format.m { render :action => 'mobile_list_deferred' }
+ 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 formatted_context_todos_path(context, :m)
+ end
+
+ def filter_to_project
+ project = current_user.projects.find(params['project']['id'])
+ redirect_to formatted_project_todos_path(project, :m)
+ end
+
+ # /todos/tag/[tag_name] shows all the actions tagged with tag_name
+ def tag
+ @source_view = params['_source_view'] || 'tag'
+ @tag_name = params[:name]
+ @page_title = "TRACKS::Tagged with \'#{@tag_name}\'"
+
+ # mobile tags are routed with :name ending on .m. So we need to chomp it
+ @tag_name = @tag_name.chomp('.m') if mobile?
+
+ @tag = Tag.find_by_name(@tag_name)
+ @tag = Tag.new(:name => @tag_name) if @tag.nil?
+ tag_collection = @tag.todos
+
+ @not_done_todos = tag_collection.find(:all,
+ :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'],
+ :order => 'todos.due IS NULL, todos.due ASC, todos.created_at ASC')
+ @hidden_todos = current_user.todos.find(:all,
+ :include => [:taggings, :tags, :context],
+ :conditions => ['tags.name = ? AND (todos.state = ? OR (contexts.hide = ? AND todos.state = ?))', @tag_name, 'project_hidden', true, 'active'],
+ :order => 'todos.completed_at DESC, todos.created_at DESC')
+ @deferred = tag_collection.find(:all,
+ :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'deferred'],
+ :order => 'show_from ASC, todos.created_at DESC')
+
+ # If you've set no_completed to zero, the completed items box isn't shown on
+ # the tag page
+ max_completed = current_user.prefs.show_number_completed
+ @done = tag_collection.find(:all,
+ :limit => max_completed,
+ :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'completed'],
+ :order => 'todos.completed_at DESC')
+
+ @projects = current_user.projects
+ @contexts = current_user.contexts
+ @contexts_to_show = @contexts.reject {|x| x.hide? }
+
+ # 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 {
+ @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
+ }
+ format.m {
+ cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
+ render :action => "mobile_tag"
+ }
+ end
+ end
+
+ def defer
+ @source_view = params['_source_view'] || 'todo'
+ numdays = params['days'].to_i
+ @todo = Todo.find(params[:id])
+ @todo.show_from = (@todo.show_from || @todo.user.date) + numdays.days
+ @saved = @todo.save
+
+ determine_down_count
+ determine_remaining_in_context_count(@todo.context_id)
+ respond_to do |format|
+ format.html { redirect_to :back }
+ format.js {render :action => 'update'}
+ end
+ end
+
+ def calendar
+ @source_view = params['_source_view'] || 'calendar'
+ @page_title = "TRACKS::Calendar"
+
+ 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
+
+ @due_today = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due <= ?', due_today_date],
+ :order => "due")
+ @due_this_week = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date],
+ :order => "due")
+ @due_next_week = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date],
+ :order => "due")
+ @due_this_month = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date],
+ :order => "due")
+ @due_after_this_month = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due > ?', due_this_month_date],
+ :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.find(:all, :order => "due")
+ render :action => 'calendar', :layout => false, :content_type => Mime::ICS
+ }
+ end
+ end
+
+ private
+
+ def get_todo_from_params
+ @todo = current_user.todos.find(params['id'])
+ 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(:with_scope, :find => {:conditions => ['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 = "Tracks - Next Actions"
+ @description = "Filter: "
+
+ 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 << " due today" if (due_within == 0)
+ @title << " due within a week" if (due_within == 6)
+ @description << " with a due date #{due_within_date_s} or earlier"
+ 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 << " actions completed"
+ @description << " in the last #{done_in_last.to_s} days"
+ 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 :with_scope, :find => {:conditions => condition_builder.to_conditions} do
+ yield
+ end
+
+ end
+
+ def with_parent_resource_scope(&block)
+ @feed_title = "Actions "
+ if (params[:context_id])
+ @context = current_user.contexts.find_by_params(params)
+ @feed_title = @feed_title + "in context '#{@context.name}'"
+ Todo.send :with_scope, :find => {:conditions => ['todos.context_id = ?', @context.id]} do
+ yield
+ end
+ elsif (params[:project_id])
+ @project = current_user.projects.find_by_params(params)
+ @feed_title = @feed_title + "in project '#{@project.name}'"
+ @project_feed = true
+ Todo.send :with_scope, :find => {:conditions => ['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 << "Lists the last #{params['limit']} incomplete next actions"
+ else
+ @description << "Lists 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 = Todo.find(:all, :conditions => ['todos.user_id = ?', current_user.id], :include => [ :project, :context, :tags ])
+
+ # Exclude hidden projects from the home page
+ @not_done_todos = Todo.find(:all,
+ :conditions => ['todos.user_id = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)',
+ current_user.id, false, 'active'],
+ :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
+ :include => [ :project, :context, :tags ])
+ 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 = Todo.find(:all,
+ :conditions => ['todos.user_id = ? AND todos.state = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)',
+ current_user.id, 'active', false, 'active'],
+ :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
+ :include => [ :project, :context, :tags ])
+ end
+
+ def determine_down_count
+ source_view do |from|
+ from.todo do
+ @down_count = Todo.count(
+ :all,
+ :conditions => ['todos.user_id = ? and todos.state = ? and contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', current_user.id, 'active', false, 'active'],
+ :include => [ :project, :context ])
+ # #@down_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos,
+ # contexts WHERE todos.context_id = contexts.id and todos.user_id = ?
+ # and todos.state = ? and contexts.hide = ?', current_user.id, 'active',
+ # false])
+ end
+ from.context do
+ @down_count = current_user.contexts.find(@todo.context_id).not_done_todo_count
+ end
+ from.project do
+ unless @todo.project_id == nil
+ @down_count = current_user.projects.find(@todo.project_id).not_done_todo_count(:include_project_hidden_todos => true)
+ @deferred_count = current_user.projects.find(@todo.project_id).deferred_todo_count
+ end
+ end
+ from.deferred do
+ @down_count = current_user.todos.count_in_state(:deferred)
+ 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
+ tag_collection = @tag.todos
+ @not_done_todos = tag_collection.find(:all, :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'])
+ @not_done_todos.empty? ? @down_count = 0 : @down_count = @not_done_todos.size
+ end
+ end
+ end
+
+ def determine_remaining_in_context_count(context_id = @todo.context_id)
+ source_view do |from|
+ from.deferred { @remaining_in_context = current_user.contexts.find(context_id).deferred_todo_count }
+ from.tag {
+ tag = Tag.find_by_name(params['_tag_name'])
+ if tag.nil?
+ tag = Tag.new(:name => params['tag'])
+ end
+ @remaining_in_context = current_user.contexts.find(context_id).not_done_todo_count({:tag => tag.id})
+ }
+ end
+ @remaining_in_context = current_user.contexts.find(context_id).not_done_todo_count if @remaining_in_context.nil?
+ end
+
+ def determine_completed_count
+ source_view do |from|
+ from.todo do
+ @completed_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos, contexts WHERE todos.context_id = contexts.id and todos.user_id = ? and todos.state = ? and contexts.hide = ?', current_user.id, 'completed', false])
+ end
+ from.context do
+ @completed_count = current_user.contexts.find(@todo.context_id).done_todo_count
+ end
+ from.project do
+ unless @todo.project_id == nil
+ @completed_count = current_user.projects.find(@todo.project_id).done_todo_count
+ end
+ end
+ end
+ end
+
+ def render_todos_html
+ lambda do
+ @page_title = "TRACKS::List tasks"
+
+ # 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.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
+
+ # Set count badge to number of not-done, not hidden context items
+ @count = 0
+ @todos.each do |x|
+ if x.active?
+ if x.project.nil?
+ @count += 1 if !x.context.hide?
+ else
+ @count += 1 if x.project.active? && !x.context.hide?
+ end
+ end
+ end
+
+ @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
+
+ render
+ end
+ end
+
+ def render_todos_mobile
+ lambda do
+ @page_title = "All actions"
+ @home = true
+ cookies[:mobile_url]= { :value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
+ 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
+ lambda do |i|
+ item_notes = sanitize(markdown( i.notes )) if i.notes?
+ due = "Due: #{format_date(i.due)}
\n" if i.due?
+ done = "Completed: #{format_date(i.completed_at)}
\n" if i.completed?
+ context_link = "#{ i.context.name }"
+ if i.project_id?
+ project_link = "#{ i.project.name }"
+ else
+ project_link = "none"
+ end
+ "#{done||''}#{due||''}#{item_notes||''}\nProject: #{project_link}
\nContext: #{context_link}
"
+ end
+ end
+
+ def render_atom_feed
+ lambda do
+ render_atom_feed_for @todos, :feed => todo_feed_options,
+ :item => {
+ :title => :description,
+ :link => lambda { |t| context_url(t.context) },
+ :description => todo_feed_content,
+ :author => lambda { |p| nil }
+ }
+ end
+ end
+
+ def render_text_feed
+ lambda do
+ render :action => 'index', :layout => false, :content_type => Mime::TEXT
+ end
+ end
+
+ def render_ical_feed
+ lambda do
+ render :action => 'index', :layout => false, :content_type => Mime::ICS
+ end
+ end
+
+ def self.is_feed_request(req)
+ ['rss','atom','txt','ics'].include?(req.parameters[:format])
+ 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.has_next_todo(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 = create_todo_from_recurring_todo(recurring_todo, 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)
+ 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 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due <= ?', due_today_date])
+ when "due_this_week"
+ return 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date])
+ when "due_next_week"
+ return 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date])
+ when "due_this_month"
+ return 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date])
+ when "due_after_this_month"
+ return 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due > ?', due_this_month_date])
+ else
+ raise Exception.new, "unknown due id for calendar: '#{id}'"
+ end
+ end
+
+ class FindConditionBuilder
+
+ def initialize
+ @queries = Array.new
+ @params = Array.new
+ end
+
+ def add(query, param)
+ @queries << query
+ @params << param
+ end
+
+ def to_conditions
+ [@queries.join(' AND ')] + @params
+ end
+ end
+
+ class TodoCreateParamsHelper
+
+ def initialize(params, prefs)
+ @params = params['request'] || params
+ @prefs = prefs
+ @attributes = params['request'] && params['request']['todo'] || params['todo']
+ end
+
+ def attributes
+ @attributes
+ end
+
+ def show_from
+ @attributes['show_from']
+ end
+
+ def due
+ @attributes['due']
+ end
+
+ def project_name
+ @params['project_name'].strip unless @params['project_name'].nil?
+ end
+
+ def context_name
+ @params['context_name'].strip unless @params['context_name'].nil?
+ end
+
+ def tag_list
+ @params['tag_list']
+ end
+
+ def parse_dates()
+ @attributes['show_from'] = @prefs.parse_date(show_from)
+ @attributes['due'] = @prefs.parse_date(due)
+ @attributes['due'] ||= ''
+ end
+
+ def project_specified_by_name?
+ return false unless @attributes['project_id'].blank?
+ return false if project_name.blank?
+ return false if project_name == 'None'
+ true
+ end
+
+ def context_specified_by_name?
+ return false unless @attributes['context_id'].blank?
+ return false if context_name.blank?
+ true
+ end
+
+ end
+end
diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb
index 142a1b3c..93726a86 100644
--- a/app/models/recurring_todo.rb
+++ b/app/models/recurring_todo.rb
@@ -623,10 +623,10 @@ class RecurringTodo < ActiveRecord::Base
def toggle_star!
if starred?
- delete_tags Todo::STARRED_TAG_NAME
+ _remove_tags Todo::STARRED_TAG_NAME
tags.reload
else
- add_tag Todo::STARRED_TAG_NAME
+ _add_tags(Todo::STARRED_TAG_NAME)
tags.reload
end
starred?
diff --git a/app/models/todo.rb b/app/models/todo.rb
index fb5539ac..97a1b984 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -118,10 +118,10 @@ class Todo < ActiveRecord::Base
def toggle_star!
if starred?
- delete_tags STARRED_TAG_NAME
+ _remove_tags STARRED_TAG_NAME
tags.reload
else
- add_tag STARRED_TAG_NAME
+ _add_tags(STARRED_TAG_NAME)
tags.reload
end
starred?
diff --git a/lib/tagging_extensions.rb b/lib/tagging_extensions.rb
index 5ef0e061..748b226f 100644
--- a/lib/tagging_extensions.rb
+++ b/lib/tagging_extensions.rb
@@ -7,11 +7,11 @@ class ActiveRecord::Base #:nodoc:
# Add tags to self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
#
# We need to avoid name conflicts with the built-in ActiveRecord association methods, thus the underscores.
- def _add_tags incoming, user
+ def _add_tags incoming
taggable?(true)
tag_cast_to_string(incoming).each do |tag_name|
begin
- tag = Tag.find_or_create_by_name(tag_name).on(self,user)
+ tag = Tag.find_or_create_by_name(tag_name)
raise Tag::Error, "tag could not be saved: #{tag_name}" if tag.new_record?
tag.taggables << self
rescue ActiveRecord::StatementInvalid => e
@@ -21,7 +21,7 @@ class ActiveRecord::Base #:nodoc:
end
# Removes tags from self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
- def _remove_tags outgoing, user
+ def _remove_tags outgoing
taggable?(true)
outgoing = tag_cast_to_string(outgoing)
@@ -36,7 +36,7 @@ class ActiveRecord::Base #:nodoc:
end
# Replace the existing tags on self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
- def tag_with list, user
+ def tag_with list
#:stopdoc:
taggable?(true)
list = tag_cast_to_string(list)
@@ -44,8 +44,8 @@ class ActiveRecord::Base #:nodoc:
# Transactions may not be ideal for you here; be aware.
Tag.transaction do
current = tags.map(&:name)
- _add_tags(list - current, user)
- _remove_tags(current - list, user)
+ _add_tags(list - current)
+ _remove_tags(current - list)
end
self
@@ -78,6 +78,7 @@ class ActiveRecord::Base #:nodoc:
when String
obj = obj.split(Tag::DELIMITER).map do |tag_name|
tag_name.strip.squeeze(" ")
+ puts "tn=#{tag_name.strip.squeeze(" ")}"
end
else
raise "Invalid object of class #{obj.class} as tagging method parameter"
diff --git a/test/unit/recurring_todo_test.rb b/test/unit/recurring_todo_test.rb
index 80106554..e7f5ef25 100644
--- a/test/unit/recurring_todo_test.rb
+++ b/test/unit/recurring_todo_test.rb
@@ -1,283 +1,283 @@
-require File.dirname(__FILE__) + '/../test_helper'
-
-class RecurringTodoTest < Test::Rails::TestCase
- fixtures :todos, :users, :contexts, :preferences, :tags, :taggings, :recurring_todos
-
- def setup
- @every_day = RecurringTodo.find(1).reload
- @every_workday = RecurringTodo.find(2).reload
- @weekly_every_day = RecurringTodo.find(3).reload
- @monthly_every_last_friday = RecurringTodo.find(4).reload
- @yearly = RecurringTodo.find(5).reload
-
- @today = Time.now.utc
- @tomorrow = @today + 1.day
- @in_three_days = Time.now.utc + 3.days
- @in_four_days = @in_three_days + 1.day # need a day after start_from
-
- @friday = Time.zone.local(2008,6,6)
- @saturday = Time.zone.local(2008,6,7)
- @sunday = Time.zone.local(2008,6,8) # june 8, 2008 was a sunday
- @monday = Time.zone.local(2008,6,9)
- @tuesday = Time.zone.local(2008,6,10)
- @wednesday = Time.zone.local(2008,6,11)
- @thursday = Time.zone.local(2008,6,12)
- end
-
- def test_pattern_text
- assert_equal "every day", @every_day.recurrence_pattern
- assert_equal "on work days", @every_workday.recurrence_pattern
- assert_equal "every last Friday of every 2 months", @monthly_every_last_friday.recurrence_pattern
- assert_equal "every year on June 8", @yearly.recurrence_pattern
- end
-
- def test_daily_every_day
- # every_day should return todays date if there was no previous date
- due_date = @every_day.get_due_date(nil)
- # use strftime in compare, because milisec / secs could be different
- assert_equal @today.strftime("%d-%m-%y"), due_date.strftime("%d-%m-%y")
-
- # when the last todo was completed today, the next todo is due tomorrow
- due_date =@every_day.get_due_date(@today)
- assert_equal @tomorrow, due_date
-
- # do something every 14 days
- @every_day.every_other1=14
- due_date = @every_day.get_due_date(@today)
- assert_equal @today+14.days, due_date
- end
-
- def test_daily_work_days
- assert_equal @monday, @every_workday.get_due_date(@friday)
- assert_equal @monday, @every_workday.get_due_date(@saturday)
- assert_equal @monday, @every_workday.get_due_date(@sunday)
- assert_equal @tuesday, @every_workday.get_due_date(@monday)
- end
-
- def test_show_from_date
- # assume that target due_date works fine, i.e. don't do the same tests over
-
- @every_day.target='show_from_date'
- # when recurrence is targeted on show_from, due date shoult remain nil
- assert_equal nil, @every_day.get_due_date(nil)
- assert_equal nil, @every_day.get_due_date(@today-3.days)
-
- # check show from get the next day
- assert_equal @today, @every_day.get_show_from_date(@today-1.days)
- assert_equal @today+1.day, @every_day.get_show_from_date(@today)
-
- @every_day.target='due_date'
- # when target on due_date, show_from is relative to due date unless delta=0
- assert_equal nil, @every_day.get_show_from_date(@today-1.days)
-
- @every_day.show_from_delta=10
- assert_equal @today, @every_day.get_show_from_date(@today+9.days) #today+1+9-10
-
- # TODO: show_from has no use case for daily pattern. Need to test on
- # weekly/monthly/yearly
- end
-
- def test_end_date_on_recurring_todo
- assert_equal true, @every_day.has_next_todo(@in_three_days)
- assert_equal true, @every_day.has_next_todo(@in_four_days)
- @every_day.end_date = @in_four_days
- assert_equal false, @every_day.has_next_todo(@in_four_days)
- end
-
- def test_weekly_every_day_setters
- @weekly_every_day.every_day = ' '
-
- @weekly_every_day.weekly_return_sunday=('s')
- assert_equal 's ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_monday=('m')
- assert_equal 'sm ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_tuesday=('t')
- assert_equal 'smt ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_wednesday=('w')
- assert_equal 'smtw ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_thursday=('t')
- assert_equal 'smtwt ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_friday=('f')
- assert_equal 'smtwtf ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_saturday=('s')
- assert_equal 'smtwtfs', @weekly_every_day.every_day
-
- # test remove
- @weekly_every_day.weekly_return_wednesday=(' ')
- assert_equal 'smt tfs', @weekly_every_day.every_day
- end
-
- def test_weekly_pattern
- assert_equal true, @weekly_every_day.has_next_todo(nil)
-
- due_date = @weekly_every_day.get_due_date(@sunday)
- assert_equal @monday, due_date
-
- # saturday is last day in week, so the next date should be sunday + n_weeks
- due_date = @weekly_every_day.get_due_date(@saturday)
- assert_equal @sunday + 2.weeks, due_date
-
- # remove tuesday and wednesday
- @weekly_every_day.weekly_return_tuesday=(' ')
- @weekly_every_day.weekly_return_wednesday=(' ')
- assert_equal 'sm tfs', @weekly_every_day.every_day
- due_date = @weekly_every_day.get_due_date(@monday)
- assert_equal @thursday, due_date
-
- @weekly_every_day.every_other1 = 1
- @weekly_every_day.every_day = ' tw '
- due_date = @weekly_every_day.get_due_date(@tuesday)
- assert_equal @wednesday, due_date
- due_date = @weekly_every_day.get_due_date(@wednesday)
- assert_equal @tuesday+1.week, due_date
- end
-
- def test_monthly_pattern
- due_date = @monthly_every_last_friday.get_due_date(@sunday)
- assert_equal Time.zone.local(2008,6,27), due_date
-
- friday_is_last_day_of_month = Time.zone.local(2008,10,31)
- due_date = @monthly_every_last_friday.get_due_date(friday_is_last_day_of_month-1.day )
- assert_equal friday_is_last_day_of_month , due_date
-
- @monthly_every_third_friday = @monthly_every_last_friday
- @monthly_every_third_friday.every_other3=3 #third
- due_date = @monthly_every_last_friday.get_due_date(@sunday) # june 8th 2008
- assert_equal Time.zone.local(2008, 6, 20), due_date
- # set date past third friday of this month
- due_date = @monthly_every_last_friday.get_due_date(Time.zone.local(2008,6,21)) # june 21th 2008
- assert_equal Time.zone.local(2008, 8, 15), due_date # every 2 months, so aug
-
- @monthly = @monthly_every_last_friday
- @monthly.recurrence_selector=0
- @monthly.every_other1 = 8 # every 8th day of the month
- @monthly.every_other2 = 2 # every 2 months
-
- due_date = @monthly.get_due_date(@saturday) # june 7th
- assert_equal @sunday, due_date # june 8th
-
- due_date = @monthly.get_due_date(@sunday) # june 8th
- assert_equal Time.zone.local(2008,8,8), due_date # aug 8th
- end
-
- def test_yearly_pattern
- # beginning of same year
- due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
- assert_equal @sunday, due_date # june 8th
-
- # same month, previous date
- due_date = @yearly.get_due_date(@saturday) # june 7th
- show_from_date = @yearly.get_show_from_date(@saturday) # june 7th
- assert_equal @sunday, due_date # june 8th
- assert_equal @sunday-5.days, show_from_date
-
- # same month, day after
- due_date = @yearly.get_due_date(@monday) # june 9th
- assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
- # very overdue
- due_date = @yearly.get_due_date(@monday+5.months-2.days) # november 7
- assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
-
- @yearly.recurrence_selector = 1
- @yearly.every_other3 = 2 # second
- @yearly.every_count = 3 # wednesday
- # beginning of same year
- due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
- assert_equal Time.zone.local(2008,6,11), due_date # june 11th
- # same month, before second wednesday
- due_date = @yearly.get_due_date(@saturday) # june 7th
- assert_equal Time.zone.local(2008,6,11), due_date # june 11th
- # same month, after second wednesday
- due_date = @yearly.get_due_date(Time.zone.local(2008,6,12)) # june 7th
- assert_equal Time.zone.local(2009,6,10), due_date # june 10th
-
- # test handling of nil
- due_date1 = @yearly.get_due_date(nil)
- due_date2 = @yearly.get_due_date(Time.now.utc + 1.day)
- assert_equal due_date1, due_date2
- end
-
- def test_last_sunday_of_march
- @yearly.recurrence_selector = 1
- @yearly.every_other2 = 3 # march
- @yearly.every_other3 = 5 # last
- @yearly.every_count = 0 # sunday
- due_date = @yearly.get_due_date(Time.zone.local(2008,10,1)) # oct 1st
- assert_equal Time.zone.local(2009,3,29), due_date # march 29th
- end
-
- def test_start_from_in_future
- # every_day should return start_day if it is in the future
- @every_day.start_from = @in_three_days
- due_date = @every_day.get_due_date(nil)
- assert_equal @in_three_days, due_date
- due_date = @every_day.get_due_date(@tomorrow)
- assert_equal @in_three_days, due_date
-
- # if we give a date in the future for the previous todo, the next to do
- # should be based on that future date.
- due_date = @every_day.get_due_date(@in_four_days)
- assert_equal @in_four_days+1.day, due_date
-
- @weekly_every_day.start_from = Time.zone.local(2020,1,1)
- assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(nil)
- assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(Time.zone.local(2019,10,1))
- assert_equal Time.zone.local(2020,1,10), @weekly_every_day.get_due_date(Time.zone.local(2020,1,9))
-
- @monthly_every_last_friday.start_from = Time.zone.local(2020,1,1)
- assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(nil) # last friday of jan
- assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(Time.zone.local(2019,12,1)) # last friday of jan
- assert_equal Time.zone.local(2020,2,28), @monthly_every_last_friday.get_due_date(Time.zone.local(2020,2,1)) # last friday of feb
-
- # start from after june 8th 2008
- @yearly.start_from = Time.zone.local(2020,6,12)
- assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(nil) # jun 8th next year
- assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2019,6,1)) # also next year
- assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2020,6,15)) # also next year
-
- this_year = Time.now.utc.year
- @yearly.start_from = Time.zone.local(this_year+1,6,12)
- due_date = @yearly.get_due_date(nil)
- assert_equal due_date.year, this_year+2
- end
-
- def test_toggle_completion
- t = @yearly
- assert_equal :active, t.current_state
- t.toggle_completion!
- assert_equal :completed, t.current_state
- t.toggle_completion!
- assert_equal :active, t.current_state
- end
-
- def test_starred
- @yearly.tag_with("1, 2, starred", User.find(@yearly.user_id))
- @yearly.tags.reload
-
- assert_equal true, @yearly.starred?
- assert_equal false, @weekly_every_day.starred?
-
- @yearly.toggle_star!
- assert_equal false, @yearly.starred?
- @yearly.toggle_star!
- assert_equal true, @yearly.starred?
- end
-
- def test_occurence_count
- @every_day.number_of_occurences = 2
- assert_equal true, @every_day.has_next_todo(@in_three_days)
- @every_day.inc_occurences
- assert_equal true, @every_day.has_next_todo(@in_three_days)
- @every_day.inc_occurences
- assert_equal false, @every_day.has_next_todo(@in_three_days)
-
- # after completion, when you reactivate the recurring todo, the occurences
- # count should be reset
- assert_equal 2, @every_day.occurences_count
- @every_day.toggle_completion!
- @every_day.toggle_completion!
- assert_equal true, @every_day.has_next_todo(@in_three_days)
- assert_equal 0, @every_day.occurences_count
- end
-
-end
+require File.dirname(__FILE__) + '/../test_helper'
+
+class RecurringTodoTest < Test::Rails::TestCase
+ fixtures :todos, :users, :contexts, :preferences, :tags, :taggings, :recurring_todos
+
+ def setup
+ @every_day = RecurringTodo.find(1).reload
+ @every_workday = RecurringTodo.find(2).reload
+ @weekly_every_day = RecurringTodo.find(3).reload
+ @monthly_every_last_friday = RecurringTodo.find(4).reload
+ @yearly = RecurringTodo.find(5).reload
+
+ @today = Time.now.utc
+ @tomorrow = @today + 1.day
+ @in_three_days = Time.now.utc + 3.days
+ @in_four_days = @in_three_days + 1.day # need a day after start_from
+
+ @friday = Time.zone.local(2008,6,6)
+ @saturday = Time.zone.local(2008,6,7)
+ @sunday = Time.zone.local(2008,6,8) # june 8, 2008 was a sunday
+ @monday = Time.zone.local(2008,6,9)
+ @tuesday = Time.zone.local(2008,6,10)
+ @wednesday = Time.zone.local(2008,6,11)
+ @thursday = Time.zone.local(2008,6,12)
+ end
+
+ def test_pattern_text
+ assert_equal "every day", @every_day.recurrence_pattern
+ assert_equal "on work days", @every_workday.recurrence_pattern
+ assert_equal "every last Friday of every 2 months", @monthly_every_last_friday.recurrence_pattern
+ assert_equal "every year on June 8", @yearly.recurrence_pattern
+ end
+
+ def test_daily_every_day
+ # every_day should return todays date if there was no previous date
+ due_date = @every_day.get_due_date(nil)
+ # use strftime in compare, because milisec / secs could be different
+ assert_equal @today.strftime("%d-%m-%y"), due_date.strftime("%d-%m-%y")
+
+ # when the last todo was completed today, the next todo is due tomorrow
+ due_date =@every_day.get_due_date(@today)
+ assert_equal @tomorrow, due_date
+
+ # do something every 14 days
+ @every_day.every_other1=14
+ due_date = @every_day.get_due_date(@today)
+ assert_equal @today+14.days, due_date
+ end
+
+ def test_daily_work_days
+ assert_equal @monday, @every_workday.get_due_date(@friday)
+ assert_equal @monday, @every_workday.get_due_date(@saturday)
+ assert_equal @monday, @every_workday.get_due_date(@sunday)
+ assert_equal @tuesday, @every_workday.get_due_date(@monday)
+ end
+
+ def test_show_from_date
+ # assume that target due_date works fine, i.e. don't do the same tests over
+
+ @every_day.target='show_from_date'
+ # when recurrence is targeted on show_from, due date shoult remain nil
+ assert_equal nil, @every_day.get_due_date(nil)
+ assert_equal nil, @every_day.get_due_date(@today-3.days)
+
+ # check show from get the next day
+ assert_equal @today, @every_day.get_show_from_date(@today-1.days)
+ assert_equal @today+1.day, @every_day.get_show_from_date(@today)
+
+ @every_day.target='due_date'
+ # when target on due_date, show_from is relative to due date unless delta=0
+ assert_equal nil, @every_day.get_show_from_date(@today-1.days)
+
+ @every_day.show_from_delta=10
+ assert_equal @today, @every_day.get_show_from_date(@today+9.days) #today+1+9-10
+
+ # TODO: show_from has no use case for daily pattern. Need to test on
+ # weekly/monthly/yearly
+ end
+
+ def test_end_date_on_recurring_todo
+ assert_equal true, @every_day.has_next_todo(@in_three_days)
+ assert_equal true, @every_day.has_next_todo(@in_four_days)
+ @every_day.end_date = @in_four_days
+ assert_equal false, @every_day.has_next_todo(@in_four_days)
+ end
+
+ def test_weekly_every_day_setters
+ @weekly_every_day.every_day = ' '
+
+ @weekly_every_day.weekly_return_sunday=('s')
+ assert_equal 's ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_monday=('m')
+ assert_equal 'sm ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_tuesday=('t')
+ assert_equal 'smt ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_wednesday=('w')
+ assert_equal 'smtw ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_thursday=('t')
+ assert_equal 'smtwt ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_friday=('f')
+ assert_equal 'smtwtf ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_saturday=('s')
+ assert_equal 'smtwtfs', @weekly_every_day.every_day
+
+ # test remove
+ @weekly_every_day.weekly_return_wednesday=(' ')
+ assert_equal 'smt tfs', @weekly_every_day.every_day
+ end
+
+ def test_weekly_pattern
+ assert_equal true, @weekly_every_day.has_next_todo(nil)
+
+ due_date = @weekly_every_day.get_due_date(@sunday)
+ assert_equal @monday, due_date
+
+ # saturday is last day in week, so the next date should be sunday + n_weeks
+ due_date = @weekly_every_day.get_due_date(@saturday)
+ assert_equal @sunday + 2.weeks, due_date
+
+ # remove tuesday and wednesday
+ @weekly_every_day.weekly_return_tuesday=(' ')
+ @weekly_every_day.weekly_return_wednesday=(' ')
+ assert_equal 'sm tfs', @weekly_every_day.every_day
+ due_date = @weekly_every_day.get_due_date(@monday)
+ assert_equal @thursday, due_date
+
+ @weekly_every_day.every_other1 = 1
+ @weekly_every_day.every_day = ' tw '
+ due_date = @weekly_every_day.get_due_date(@tuesday)
+ assert_equal @wednesday, due_date
+ due_date = @weekly_every_day.get_due_date(@wednesday)
+ assert_equal @tuesday+1.week, due_date
+ end
+
+ def test_monthly_pattern
+ due_date = @monthly_every_last_friday.get_due_date(@sunday)
+ assert_equal Time.zone.local(2008,6,27), due_date
+
+ friday_is_last_day_of_month = Time.zone.local(2008,10,31)
+ due_date = @monthly_every_last_friday.get_due_date(friday_is_last_day_of_month-1.day )
+ assert_equal friday_is_last_day_of_month , due_date
+
+ @monthly_every_third_friday = @monthly_every_last_friday
+ @monthly_every_third_friday.every_other3=3 #third
+ due_date = @monthly_every_last_friday.get_due_date(@sunday) # june 8th 2008
+ assert_equal Time.zone.local(2008, 6, 20), due_date
+ # set date past third friday of this month
+ due_date = @monthly_every_last_friday.get_due_date(Time.zone.local(2008,6,21)) # june 21th 2008
+ assert_equal Time.zone.local(2008, 8, 15), due_date # every 2 months, so aug
+
+ @monthly = @monthly_every_last_friday
+ @monthly.recurrence_selector=0
+ @monthly.every_other1 = 8 # every 8th day of the month
+ @monthly.every_other2 = 2 # every 2 months
+
+ due_date = @monthly.get_due_date(@saturday) # june 7th
+ assert_equal @sunday, due_date # june 8th
+
+ due_date = @monthly.get_due_date(@sunday) # june 8th
+ assert_equal Time.zone.local(2008,8,8), due_date # aug 8th
+ end
+
+ def test_yearly_pattern
+ # beginning of same year
+ due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
+ assert_equal @sunday, due_date # june 8th
+
+ # same month, previous date
+ due_date = @yearly.get_due_date(@saturday) # june 7th
+ show_from_date = @yearly.get_show_from_date(@saturday) # june 7th
+ assert_equal @sunday, due_date # june 8th
+ assert_equal @sunday-5.days, show_from_date
+
+ # same month, day after
+ due_date = @yearly.get_due_date(@monday) # june 9th
+ assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
+ # very overdue
+ due_date = @yearly.get_due_date(@monday+5.months-2.days) # november 7
+ assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
+
+ @yearly.recurrence_selector = 1
+ @yearly.every_other3 = 2 # second
+ @yearly.every_count = 3 # wednesday
+ # beginning of same year
+ due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
+ assert_equal Time.zone.local(2008,6,11), due_date # june 11th
+ # same month, before second wednesday
+ due_date = @yearly.get_due_date(@saturday) # june 7th
+ assert_equal Time.zone.local(2008,6,11), due_date # june 11th
+ # same month, after second wednesday
+ due_date = @yearly.get_due_date(Time.zone.local(2008,6,12)) # june 7th
+ assert_equal Time.zone.local(2009,6,10), due_date # june 10th
+
+ # test handling of nil
+ due_date1 = @yearly.get_due_date(nil)
+ due_date2 = @yearly.get_due_date(Time.now.utc + 1.day)
+ assert_equal due_date1, due_date2
+ end
+
+ def test_last_sunday_of_march
+ @yearly.recurrence_selector = 1
+ @yearly.every_other2 = 3 # march
+ @yearly.every_other3 = 5 # last
+ @yearly.every_count = 0 # sunday
+ due_date = @yearly.get_due_date(Time.zone.local(2008,10,1)) # oct 1st
+ assert_equal Time.zone.local(2009,3,29), due_date # march 29th
+ end
+
+ def test_start_from_in_future
+ # every_day should return start_day if it is in the future
+ @every_day.start_from = @in_three_days
+ due_date = @every_day.get_due_date(nil)
+ assert_equal @in_three_days, due_date
+ due_date = @every_day.get_due_date(@tomorrow)
+ assert_equal @in_three_days, due_date
+
+ # if we give a date in the future for the previous todo, the next to do
+ # should be based on that future date.
+ due_date = @every_day.get_due_date(@in_four_days)
+ assert_equal @in_four_days+1.day, due_date
+
+ @weekly_every_day.start_from = Time.zone.local(2020,1,1)
+ assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(nil)
+ assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(Time.zone.local(2019,10,1))
+ assert_equal Time.zone.local(2020,1,10), @weekly_every_day.get_due_date(Time.zone.local(2020,1,9))
+
+ @monthly_every_last_friday.start_from = Time.zone.local(2020,1,1)
+ assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(nil) # last friday of jan
+ assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(Time.zone.local(2019,12,1)) # last friday of jan
+ assert_equal Time.zone.local(2020,2,28), @monthly_every_last_friday.get_due_date(Time.zone.local(2020,2,1)) # last friday of feb
+
+ # start from after june 8th 2008
+ @yearly.start_from = Time.zone.local(2020,6,12)
+ assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(nil) # jun 8th next year
+ assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2019,6,1)) # also next year
+ assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2020,6,15)) # also next year
+
+ this_year = Time.now.utc.year
+ @yearly.start_from = Time.zone.local(this_year+1,6,12)
+ due_date = @yearly.get_due_date(nil)
+ assert_equal due_date.year, this_year+2
+ end
+
+ def test_toggle_completion
+ t = @yearly
+ assert_equal :active, t.current_state
+ t.toggle_completion!
+ assert_equal :completed, t.current_state
+ t.toggle_completion!
+ assert_equal :active, t.current_state
+ end
+
+ def test_starred
+ @yearly.tag_with("1, 2, starred")
+ @yearly.tags.reload
+
+ assert_equal true, @yearly.starred?
+ assert_equal false, @weekly_every_day.starred?
+
+ @yearly.toggle_star!
+ assert_equal false, @yearly.starred?
+ @yearly.toggle_star!
+ assert_equal true, @yearly.starred?
+ end
+
+ def test_occurence_count
+ @every_day.number_of_occurences = 2
+ assert_equal true, @every_day.has_next_todo(@in_three_days)
+ @every_day.inc_occurences
+ assert_equal true, @every_day.has_next_todo(@in_three_days)
+ @every_day.inc_occurences
+ assert_equal false, @every_day.has_next_todo(@in_three_days)
+
+ # after completion, when you reactivate the recurring todo, the occurences
+ # count should be reset
+ assert_equal 2, @every_day.occurences_count
+ @every_day.toggle_completion!
+ @every_day.toggle_completion!
+ assert_equal true, @every_day.has_next_todo(@in_three_days)
+ assert_equal 0, @every_day.occurences_count
+ end
+
+end
diff --git a/test/unit/todo_test.rb b/test/unit/todo_test.rb
index 85c69b39..3e0fdf4d 100644
--- a/test/unit/todo_test.rb
+++ b/test/unit/todo_test.rb
@@ -1,181 +1,181 @@
-require File.dirname(__FILE__) + '/../test_helper'
-require 'date'
-
-class TodoTest < Test::Rails::TestCase
- fixtures :todos, :users, :contexts, :preferences, :tags, :taggings
-
- def setup
- @not_completed1 = Todo.find(1).reload
- @not_completed2 = Todo.find(2).reload
- @completed = Todo.find(8).reload
- end
-
- # Test loading a todo item
- def test_load
- assert_kind_of Todo, @not_completed1
- assert_equal 1, @not_completed1.id
- assert_equal 1, @not_completed1.context_id
- assert_equal 2, @not_completed1.project_id
- assert_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description
- assert_nil @not_completed1.notes
- assert @not_completed1.completed? == false
- assert_equal 1.week.ago.beginning_of_day.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M")
- assert_equal 2.week.from_now.beginning_of_day.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d")
- assert_nil @not_completed1.completed_at
- assert_equal 1, @not_completed1.user_id
- end
-
- def test_completed
- assert_kind_of Todo, @completed
- assert @completed.completed?
- assert_not_nil @completed.completed_at
- end
-
- def test_completed_at_cleared_after_toggle_to_active
- assert_kind_of Todo, @completed
- assert @completed.completed?
- @completed.toggle_completion!
- assert @completed.active?
- assert_nil @completed.completed_at
- end
-
-
- # Validation tests
- #
- def test_validate_presence_of_description
- assert_equal "Call dinosaur exterminator", @not_completed2.description
- @not_completed2.description = ""
- assert !@not_completed2.save
- assert_equal 1, @not_completed2.errors.count
- assert_equal "can't be blank", @not_completed2.errors.on(:description)
- end
-
- def test_validate_length_of_description
- assert_equal "Call dinosaur exterminator", @not_completed2.description
- @not_completed2.description = generate_random_string(101)
- assert !@not_completed2.save
- assert_equal 1, @not_completed2.errors.count
- assert_equal "is too long (maximum is 100 characters)", @not_completed2.errors.on(:description)
- end
-
- def test_validate_length_of_notes
- assert_equal "Ask him if I need to hire a skip for the corpses.", @not_completed2.notes
- @not_completed2.notes = generate_random_string(60001)
- assert !@not_completed2.save
- assert_equal 1, @not_completed2.errors.count
- assert_equal "is too long (maximum is 60000 characters)", @not_completed2.errors.on(:notes)
- end
-
- def test_validate_show_from_must_be_a_date_in_the_future
- t = @not_completed2
- t[:show_from] = 1.week.ago # we have to set this via the indexer because show_from=() updates the state
- # and actual show_from value appropriately based on the date
- assert !t.save
- assert_equal 1, t.errors.count
- assert_equal "must be a date in the future", t.errors.on(:show_from)
- end
-
- def test_defer_an_existing_todo
- @not_completed2
- assert_equal :active, @not_completed2.current_state
- @not_completed2.show_from = next_week
- assert @not_completed2.save, "should have saved successfully" + @not_completed2.errors.to_xml
- assert_equal :deferred, @not_completed2.current_state
- end
-
- def test_create_a_new_deferred_todo
- user = users(:other_user)
- todo = user.todos.build
- todo.show_from = next_week
- todo.context_id = 1
- todo.description = 'foo'
- assert todo.save, "should have saved successfully" + todo.errors.to_xml
- assert_equal :deferred, todo.current_state
- end
-
- def test_create_a_new_deferred_todo_by_passing_attributes
- user = users(:other_user)
- todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo')
- assert todo.save, "should have saved successfully" + todo.errors.to_xml
- assert_equal :deferred, todo.current_state
- end
-
- def test_feed_options
- opts = Todo.feed_options(users(:admin_user))
- assert_equal 'Tracks Actions', opts[:title], 'Unexpected value for :title key of feed_options'
- assert_equal 'Actions for Admin Schmadmin', opts[:description], 'Unexpected value for :description key of feed_options'
- end
-
- def test_toggle_completion
- t = @not_completed1
- assert_equal :active, t.current_state
- t.toggle_completion!
- assert_equal :completed, t.current_state
- t.toggle_completion!
- assert_equal :active, t.current_state
- end
-
- def test_activate_also_saves
- t = @not_completed1
- t.show_from = 1.week.from_now
- t.save!
- assert t.deferred?
- t.reload
- t.activate!
- assert t.active?
- t.reload
- assert t.active?
- end
-
- def test_project_returns_null_object_when_nil
- t = @not_completed1
- assert !t.project.is_a?(NullProject)
- t.project = nil
- assert t.project.is_a?(NullProject)
- end
-
- def test_initial_state_defaults_to_active
- t = Todo.new
- t.description = 'foo'
- t.context_id = 1
- t.save!
- t.reload
- assert_equal :active, t.current_state
- end
-
- def test_initial_state_is_deferred_when_show_from_in_future
- t = Todo.new
- t.user = users(:admin_user)
- t.description = 'foo'
- t.context_id = 1
- t.show_from = 1.week.from_now.to_date
- t.save!
- t.reload
- assert_equal :deferred, t.current_state
- end
-
- def test_todo_is_not_starred
- assert !@not_completed1.starred?
- end
-
- def test_todo_2_is_not_starred
- assert !Todo.find(2).starred?
- end
-
- def test_todo_is_starred_after_starred_tag_is_added
- @not_completed1.add_tag('starred')
- assert @not_completed1.starred?
- end
-
- def test_todo_is_starred_after_toggle_starred
- @not_completed1.toggle_star!
- assert @not_completed1.starred?
- end
-
- def test_todo_is_not_starred_after_toggle_starred_twice
- @not_completed1.toggle_star!
- @not_completed1.toggle_star!
- assert !@not_completed1.starred?
- end
-
-end
+require File.dirname(__FILE__) + '/../test_helper'
+require 'date'
+
+class TodoTest < Test::Rails::TestCase
+ fixtures :todos, :users, :contexts, :preferences, :tags, :taggings
+
+ def setup
+ @not_completed1 = Todo.find(1).reload
+ @not_completed2 = Todo.find(2).reload
+ @completed = Todo.find(8).reload
+ end
+
+ # Test loading a todo item
+ def test_load
+ assert_kind_of Todo, @not_completed1
+ assert_equal 1, @not_completed1.id
+ assert_equal 1, @not_completed1.context_id
+ assert_equal 2, @not_completed1.project_id
+ assert_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description
+ assert_nil @not_completed1.notes
+ assert @not_completed1.completed? == false
+ assert_equal 1.week.ago.beginning_of_day.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M")
+ assert_equal 2.week.from_now.beginning_of_day.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d")
+ assert_nil @not_completed1.completed_at
+ assert_equal 1, @not_completed1.user_id
+ end
+
+ def test_completed
+ assert_kind_of Todo, @completed
+ assert @completed.completed?
+ assert_not_nil @completed.completed_at
+ end
+
+ def test_completed_at_cleared_after_toggle_to_active
+ assert_kind_of Todo, @completed
+ assert @completed.completed?
+ @completed.toggle_completion!
+ assert @completed.active?
+ assert_nil @completed.completed_at
+ end
+
+
+ # Validation tests
+ #
+ def test_validate_presence_of_description
+ assert_equal "Call dinosaur exterminator", @not_completed2.description
+ @not_completed2.description = ""
+ assert !@not_completed2.save
+ assert_equal 1, @not_completed2.errors.count
+ assert_equal "can't be blank", @not_completed2.errors.on(:description)
+ end
+
+ def test_validate_length_of_description
+ assert_equal "Call dinosaur exterminator", @not_completed2.description
+ @not_completed2.description = generate_random_string(101)
+ assert !@not_completed2.save
+ assert_equal 1, @not_completed2.errors.count
+ assert_equal "is too long (maximum is 100 characters)", @not_completed2.errors.on(:description)
+ end
+
+ def test_validate_length_of_notes
+ assert_equal "Ask him if I need to hire a skip for the corpses.", @not_completed2.notes
+ @not_completed2.notes = generate_random_string(60001)
+ assert !@not_completed2.save
+ assert_equal 1, @not_completed2.errors.count
+ assert_equal "is too long (maximum is 60000 characters)", @not_completed2.errors.on(:notes)
+ end
+
+ def test_validate_show_from_must_be_a_date_in_the_future
+ t = @not_completed2
+ t[:show_from] = 1.week.ago # we have to set this via the indexer because show_from=() updates the state
+ # and actual show_from value appropriately based on the date
+ assert !t.save
+ assert_equal 1, t.errors.count
+ assert_equal "must be a date in the future", t.errors.on(:show_from)
+ end
+
+ def test_defer_an_existing_todo
+ @not_completed2
+ assert_equal :active, @not_completed2.current_state
+ @not_completed2.show_from = next_week
+ assert @not_completed2.save, "should have saved successfully" + @not_completed2.errors.to_xml
+ assert_equal :deferred, @not_completed2.current_state
+ end
+
+ def test_create_a_new_deferred_todo
+ user = users(:other_user)
+ todo = user.todos.build
+ todo.show_from = next_week
+ todo.context_id = 1
+ todo.description = 'foo'
+ assert todo.save, "should have saved successfully" + todo.errors.to_xml
+ assert_equal :deferred, todo.current_state
+ end
+
+ def test_create_a_new_deferred_todo_by_passing_attributes
+ user = users(:other_user)
+ todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo')
+ assert todo.save, "should have saved successfully" + todo.errors.to_xml
+ assert_equal :deferred, todo.current_state
+ end
+
+ def test_feed_options
+ opts = Todo.feed_options(users(:admin_user))
+ assert_equal 'Tracks Actions', opts[:title], 'Unexpected value for :title key of feed_options'
+ assert_equal 'Actions for Admin Schmadmin', opts[:description], 'Unexpected value for :description key of feed_options'
+ end
+
+ def test_toggle_completion
+ t = @not_completed1
+ assert_equal :active, t.current_state
+ t.toggle_completion!
+ assert_equal :completed, t.current_state
+ t.toggle_completion!
+ assert_equal :active, t.current_state
+ end
+
+ def test_activate_also_saves
+ t = @not_completed1
+ t.show_from = 1.week.from_now
+ t.save!
+ assert t.deferred?
+ t.reload
+ t.activate!
+ assert t.active?
+ t.reload
+ assert t.active?
+ end
+
+ def test_project_returns_null_object_when_nil
+ t = @not_completed1
+ assert !t.project.is_a?(NullProject)
+ t.project = nil
+ assert t.project.is_a?(NullProject)
+ end
+
+ def test_initial_state_defaults_to_active
+ t = Todo.new
+ t.description = 'foo'
+ t.context_id = 1
+ t.save!
+ t.reload
+ assert_equal :active, t.current_state
+ end
+
+ def test_initial_state_is_deferred_when_show_from_in_future
+ t = Todo.new
+ t.user = users(:admin_user)
+ t.description = 'foo'
+ t.context_id = 1
+ t.show_from = 1.week.from_now.to_date
+ t.save!
+ t.reload
+ assert_equal :deferred, t.current_state
+ end
+
+ def test_todo_is_not_starred
+ assert !@not_completed1.starred?
+ end
+
+ def test_todo_2_is_not_starred
+ assert !Todo.find(2).starred?
+ end
+
+ def test_todo_is_starred_after_starred_tag_is_added
+ @not_completed1._add_tags('starred')
+ assert @not_completed1.starred?
+ end
+
+ def test_todo_is_starred_after_toggle_starred
+ @not_completed1.toggle_star!
+ assert @not_completed1.starred?
+ end
+
+ def test_todo_is_not_starred_after_toggle_starred_twice
+ @not_completed1.toggle_star!
+ @not_completed1.toggle_star!
+ assert !@not_completed1.starred?
+ end
+
+end