diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 4c63b89f..550a8fca 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -1,268 +1,266 @@ -# 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 - +# 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 => SITE_CONFIG['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 + + 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/projects_controller.rb b/app/controllers/projects_controller.rb index 4e739fe9..1baca4b6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -175,7 +175,7 @@ class ProjectsController < ApplicationController def order project_ids = params["list-active-projects"] || params["list-hidden-projects"] || params["list-completed-projects"] - projects = current_user.projects.update_positions( project_ids ) + @projects = current_user.projects.update_positions( project_ids ) render :nothing => true rescue notify :error, $! diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb index aaed6a33..0784a606 100644 --- a/app/controllers/recurring_todos_controller.rb +++ b/app/controllers/recurring_todos_controller.rb @@ -1,268 +1,266 @@ -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 +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 + + # make sure that we set weekly_return_xxx to empty (space) when they are + # not checked (and thus not present in params["recurring_todo"]) + %w{"monday","tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"}.each do |day| + params["recurring_todo"]["weekly_return_"+day]=' ' if params["recurring_todo"]["weekly_return_"+day].nil? + end + + @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 \ No newline at end of file diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 0c604533..872b4fdd 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -14,8 +14,8 @@ class SearchController < ApplicationController @found_tags = Tagging.find_by_sql([ "SELECT DISTINCT tags.name as name "+ "FROM tags "+ - "LEFT JOIN taggings ON tags.id = taggings.tag_id "+ - "LEFT JOIN todos ON taggings.taggable_id = todos.id "+ + "LEFT JOIN taggings ON tags.id = taggings.tag_id "+ + "LEFT JOIN todos ON taggings.taggable_id = todos.id "+ "WHERE todos.user_id=? "+ "AND tags.name LIKE ? ", current_user.id, terms])