Merge branch 'master' of git://github.com/TracksApp/tracks into upstream

This commit is contained in:
Hans de Graaff 2012-07-21 11:25:14 +02:00
commit 66f0859e63
256 changed files with 7258 additions and 20756 deletions

25
Gemfile
View file

@ -1,6 +1,6 @@
source 'https://rubygems.org'
gem 'rails'
gem 'rails'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
@ -24,19 +24,19 @@ gem "rails_autolink"
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem 'sass-rails'
gem 'coffee-rails'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', :platform => :ruby
gem 'therubyracer', :platform => :ruby
gem 'uglifier', '>= 1.0.3'
gem 'uglifier'
end
gem 'jquery-rails'
# To use ActiveModel has_secure_password
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'bcrypt-ruby'
# To use Jbuilder templates for JSON
# gem 'jbuilder'
@ -50,29 +50,22 @@ gem 'bcrypt-ruby', '~> 3.0.0'
group :development do
if RUBY_VERSION.to_f >= 1.9
# gem "ruby-debug19", :require => 'ruby-debug'
gem "mongrel", "1.2.0.pre2"
gem "mongrel", ">=1.2.0.pre2"
else
gem "ruby-debug"
gem "mongrel"
end
gem "yard"
gem "tolk"
end
group :test do
# gem "test-unit", "1.2.3"
# gem "flexmock"
# gem "ZenTest", ">=4.0.0"
# gem "hpricot"
# gem "hoe"
# gem "rspec-rails", "~>1.3.3"
# gem 'memory_test_fix', '~>0.1.3'
gem "factory_girl_rails"
gem "capybara"
gem "selenium-webdriver" # Note that > 2.14 has problems: https://code.google.com/p/selenium/issues/detail?id=3075
gem "database_cleaner"
gem "cucumber-rails"
gem "aruba"
gem "therubyracer"
# uncomment to use the webkit option. This depends on Qt to be installed
#gem "capybara-webkit"
@ -80,4 +73,4 @@ group :test do
# uncomment to be able to make screenshots from scenarios
#gem "capybara-screenshot"
#gem "launchy"
end
end

View file

@ -2,7 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
RedCloth (4.2.9)
aasm (3.0.7)
aasm (3.0.8)
actionmailer (3.2.6)
actionpack (= 3.2.6)
mail (~> 2.4.4)
@ -48,7 +48,7 @@ GEM
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
childprocess (0.3.2)
childprocess (0.3.3)
ffi (~> 1.0.6)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
@ -72,10 +72,10 @@ GEM
erubis (2.7.0)
execjs (1.4.0)
multi_json (~> 1.0)
factory_girl (3.4.0)
factory_girl (3.5.0)
activesupport (>= 3.0.0)
factory_girl_rails (3.4.0)
factory_girl (~> 3.4.0)
factory_girl_rails (3.5.0)
factory_girl (~> 3.5.0)
railties (>= 3.0.0)
ffi (1.0.11)
formatize (1.1.0)
@ -83,7 +83,7 @@ GEM
actionpack (~> 3.0)
bluecloth (~> 2.2)
gem_plugin (0.2.3)
gherkin (2.11.0)
gherkin (2.11.1)
json (>= 1.4.6)
hike (1.2.1)
htmlentities (4.3.1)
@ -94,19 +94,19 @@ GEM
thor (~> 0.14)
json (1.7.3)
libv8 (3.3.10.4)
libwebsocket (0.1.3)
libwebsocket (0.1.4)
addressable
mail (2.4.4)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.18)
mime-types (1.19)
mongrel (1.2.0.pre2)
daemons (~> 1.0.10)
gem_plugin (~> 0.2.3)
multi_json (1.3.6)
mysql2 (0.3.11)
nokogiri (1.5.4)
nokogiri (1.5.5)
polyglot (0.3.3)
rack (1.4.1)
rack-cache (1.2)
@ -135,25 +135,24 @@ GEM
rake (0.9.2.2)
rdoc (3.12)
json (~> 1.4)
rspec (2.10.0)
rspec-core (~> 2.10.0)
rspec-expectations (~> 2.10.0)
rspec-mocks (~> 2.10.0)
rspec-core (2.10.1)
rspec-expectations (2.10.0)
rspec (2.11.0)
rspec-core (~> 2.11.0)
rspec-expectations (~> 2.11.0)
rspec-mocks (~> 2.11.0)
rspec-core (2.11.0)
rspec-expectations (2.11.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.10.1)
rubyzip (0.9.8)
rspec-mocks (2.11.1)
rubyzip (0.9.9)
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sass (3.1.19)
sass (3.1.20)
sass-rails (3.2.5)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
selenium-webdriver (2.22.2)
selenium-webdriver (2.24.0)
childprocess (>= 0.2.5)
ffi (~> 1.0)
libwebsocket (~> 0.1.3)
multi_json (~> 1.0)
rubyzip
@ -167,18 +166,22 @@ GEM
rails (>= 3.1)
therubyracer (0.10.1)
libv8 (~> 3.3.10)
thor (0.15.2)
thor (0.15.4)
tilt (1.3.3)
tolk (1.3.1)
will_paginate
ya2yaml (~> 0.26)
treetop (1.4.10)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.33)
uglifier (1.2.4)
uglifier (1.2.6)
execjs (>= 0.3.0)
multi_json (>= 1.0.2)
multi_json (~> 1.3)
will_paginate (3.0.3)
xpath (0.1.4)
nokogiri (~> 1.3)
ya2yaml (0.31)
yard (0.8.2.1)
PLATFORMS
@ -189,25 +192,26 @@ DEPENDENCIES
aasm
acts_as_list
aruba
bcrypt-ruby (~> 3.0.0)
bcrypt-ruby
capybara
coffee-rails (~> 3.2.1)
coffee-rails
cucumber-rails
database_cleaner
factory_girl_rails
formatize
htmlentities
jquery-rails
mongrel (= 1.2.0.pre2)
mongrel (>= 1.2.0.pre2)
mysql2
rails
rails_autolink
sanitize
sass-rails (~> 3.2.3)
sass-rails
selenium-webdriver
sqlite3
swf_fu
therubyracer
uglifier (>= 1.0.3)
tolk
uglifier
will_paginate
yard

View file

@ -17,7 +17,8 @@
//= require tracks.js
// Stuff in vendor/assets
//= require jquery-ui-1.8.17.custom.min
//= require jquery-ui-1.8.21.custom.min
//= require jquery.ui.touch-punch.min
//= require jquery.blockUI
//= require jquery.cookie
//= require jquery.form
@ -27,4 +28,4 @@
//= require niftycube
//= require superfish
//= require supersubs
//= require swfobject
//= require swf_fu

View file

@ -35,7 +35,7 @@ var TracksForm = {
$('#project_name').html(name);
},
set_tag_list: function (name) {
$('input#todo_tag_list').val(name);
$('input#tag_list').val(name);
},
set_tag_list_for_multi_add: function (name) {
$('#multi_tag_list').val(name);
@ -252,7 +252,6 @@ var TracksPages = {
ContextItems.setup_autocomplete_for_contexts('input[name=context_name]');
ContextItems.setup_autocomplete_for_contexts('input[id="project_default_context_name"]');
TracksPages.setup_autocomplete_for_tag_list('input[name=tag_list]'); // todo edit form
TracksPages.setup_autocomplete_for_tag_list('input[name=todo_tag_list]'); // new todo form
TracksPages.setup_autocomplete_for_tag_list('input[id="project_default_tags"]');
TodoItems.setup_autocomplete_for_predecessor();
},
@ -519,8 +518,8 @@ var TodoItems = {
ui.draggable.remove();
$('.drop_target').hide();
ajax_options = default_ajax_options_for_scripts('POST', relative_to_root('todos/change_context'), target);
ajax_options.data += "&todo[id]="+dragged_todo + "&todo[context_id]="+context_id
ajax_options = default_ajax_options_for_scripts('POST', relative_to_root('todos/'+dragged_todo + '/change_context'), target);
ajax_options.data += "&todo[context_id]="+context_id
$.ajax(ajax_options);
},
setup_drag_and_drop: function() {

View file

@ -230,7 +230,7 @@ table.c {
display:inline;
}
input#todo_description, input#todo_tag_list, textarea#todo_notes, select#todo_project_id, select#todo_context_id {
input#todo_description, input#tag_list, textarea#todo_notes, select#todo_project_id, select#todo_context_id {
width: 95%;
}

View file

@ -100,11 +100,7 @@ class ApplicationController < ActionController::Base
end
def count_deferred_todos(todos_parent)
if todos_parent.nil?
count = 0
else
count = todos_parent.todos.deferred.count
end
return todos_parent.nil? ? 0 : eval("@#{todos_parent.class.to_s.downcase}_deferred_counts[#{todos_parent.id}]") || 0
end
# Convert a date object to the format specified in the user's preferences in
@ -131,8 +127,8 @@ class ApplicationController < ActionController::Base
# 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
# 'mobile' version designed to be suitable for use from a phone or PDA. It
# makes some sense that the 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
@ -284,7 +280,8 @@ class ApplicationController < ActionController::Base
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)")
eval("@#{parent}_not_done_counts ||= current_user.todos.active.group('#{parent}_id').count")
eval("@#{parent}_deferred_counts ||= current_user.todos.deferred.group('#{parent}_id').count")
end
end

View file

@ -1,324 +0,0 @@
# 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"
class ApplicationController < ActionController::Base
protect_from_forgery
helper :application
include LoginSystem
helper_method :current_user, :prefs, :format_date
layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
exempt_from_layout /\.js\.erb$/
before_filter :check_for_deprecated_password_hash
before_filter :set_session_expiration
before_filter :set_time_zone
before_filter :set_zindex_counter
before_filter :set_locale
prepend_before_filter :login_required
prepend_before_filter :enable_mobile_content_negotiation
after_filter :set_charset
# 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_locale
locale = params[:locale] # specifying a locale in the request takes precedence
locale = locale || prefs.locale unless current_user.nil? # otherwise, the locale of the currently logged in user takes over
locale = locale || request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first if request.env['HTTP_ACCEPT_LANGUAGE']
I18n.locale = locale.nil? ? I18n.default_locale : (I18n::available_locales.include?(locale.to_sym) ? locale : I18n.default_locale)
logger.debug("Selected '#{I18n.locale}' as locale")
end
def set_session_expiration
# http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
unless session == nil
return if self.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
# Redirects to change_password_user_path if the current user uses a
# deprecated password hashing algorithm.
def check_for_deprecated_password_hash
if current_user and current_user.uses_deprecated_password?
notify :warning, t('users.you_have_to_reset_your_password')
redirect_to change_password_user_path current_user
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)
count = count_undone_todos(todos_parent)
deferred_count = count_deferred_todos(todos_parent)
if count == 0 && deferred_count > 0
word = I18n.t('common.actions_midsentence', :count => deferred_count)
word = I18n.t('common.deferred') + "&nbsp;" + word
return deferred_count.to_s + "&nbsp;" + word
else
word = I18n.t('common.actions_midsentence', :count => count)
return count.to_s + "&nbsp;" + word
end
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
def count_deferred_todos(todos_parent)
if todos_parent.nil?
count = 0
else
count = todos_parent.todos.deferred.count
end
end
# Convert a date object to the format specified in the user's preferences in
# config/settings.yml
#
def format_date(date)
return date ? date.in_time_zone(prefs.time_zone).strftime("#{prefs.date_format}") : ''
end
def for_autocomplete(coll, substr)
if substr # protect agains empty request
filtered = coll.find_all{|item| item.name.downcase.include? substr.downcase}
json_elems = Array[*filtered.map{ |e| {:id => e.id.to_s, :value => e.name} }].to_json
return json_elems
else
return ""
end
end
def format_dependencies_as_json_for_auto_complete(entries)
json_elems = Array[*entries.map{ |e| {:value => e.id.to_s, :label => e.specification} }].to_json
return json_elems
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})
todo.recurring_todo_id = rt.id
# set dates
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
def handle_unverified_request
unless request.format=="application/xml"
super # handle xml http auth via our own login code
end
end
protected
def admin_login_required
unless User.find_by_id_and_is_admin(session['user_id'], true)
render :text => t('errors.user_unauthorized'), :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
def self.cas_enabled?
Tracks::Config.cas_enabled?
end
def cas_enabled?
self.class.cas_enabled?
end
def self.prefered_auth?
Tracks::Config.prefered_auth?
end
def prefered_auth?
self.class.prefered_auth?
end
# all completed todos [today@00:00, today@now]
def get_done_today(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_day = Time.zone.now.beginning_of_day
completed_todos.completed_after(start_of_this_day).all(includes)
end
# all completed todos [begin_of_week, start_of_today]
def get_done_this_week(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_week = Time.zone.now.beginning_of_week
start_of_this_day = Time.zone.now.beginning_of_day
completed_todos.completed_before(start_of_this_day).completed_after(start_of_this_week).all(includes)
end
# all completed todos [begin_of_month, begin_of_week]
def get_done_this_month(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES})
start_of_this_month = Time.zone.now.beginning_of_month
start_of_this_week = Time.zone.now.beginning_of_week
completed_todos.completed_before(start_of_this_week).completed_after(start_of_this_month).all(includes)
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
def set_zindex_counter
# this counter can be used to handle the IE z-index bug
@z_index_counter = 500
end
end

View file

@ -67,6 +67,7 @@ class ContextsController < ApplicationController
respond_to do |format|
format.js do
@down_count = current_user.contexts.size
init_not_done_counts
end
format.xml do
if @context.new_record?

View file

@ -2,7 +2,7 @@ class IntegrationsController < ApplicationController
require 'mail'
skip_before_filter :login_required, :only => [:cloudmailin, :search_plugin, :google_gadget]
def index
@page_title = 'TRACKS::Integrations'
end
@ -10,28 +10,22 @@ class IntegrationsController < ApplicationController
def rest_api
@page_title = 'TRACKS::REST API Documentation'
end
def get_quicksilver_applescript
context = current_user.contexts.find params[:context_id]
render :partial => 'quicksilver_applescript', :locals => { :context => context }
get_applescript('quicksilver_applescript')
end
def get_applescript1
context = current_user.contexts.find params[:context_id]
render :partial => 'applescript1', :locals => { :context => context }
get_applescript('applescript1')
end
def get_applescript2
context = current_user.contexts.find params[:context_id]
render :partial => 'applescript2', :locals => { :context => context }
get_applescript('applescript2')
end
def search_plugin
# TODO: ASSET PATH!!
@icon_data = [File.open(Rails.root + '/app/assets/images/done.png').read].
@icon_data = [File.open(File.join(Rails.root, 'app', 'assets', 'images', 'done.png')).read].
pack('m').gsub(/\n/, '')
render :layout => false
end
def google_gadget
@ -49,38 +43,18 @@ class IntegrationsController < ApplicationController
return false
end
# parse message
message = Mail.new(params[:message])
# find user
user = User.where("preferences.sms_email = ?", message.from).includes(:preference).first
if user.nil?
render :text => "No user found", :status => 404
return false
end
# load user settings
context = user.prefs.sms_context
# prepare body
if message.body.multipart?
body = message.body.preamble
if MessageGateway::receive(Mail.new(params[:message]))
render :text => 'success', :status => 200
else
body = message.body.to_s
render :text => "No user found or other error", :status => 404
end
# parse mail
if message.subject.to_s.empty?
description = body
notes = nil
else
description = message.subject.to_s
notes = body
end
# create todo
todo = Todo.from_rich_message(user, context.id, description, notes)
todo.save!
render :text => 'success', :status => 200
end
private
def get_applescript(partial_name)
context = current_user.contexts.find params[:context_id]
render :partial => partial_name, :locals => { :context => context }
end
end

View file

@ -31,14 +31,13 @@ class ProjectsController < ApplicationController
@completed_count = current_user.projects.completed.count
@no_projects = current_user.projects.empty?
current_user.projects.cache_note_counts
@new_project = current_user.projects.build
end
format.m do
@completed_projects = current_user.projects.completed
@down_count = @active_projects.size + @hidden_projects.size + @completed_projects.size
cookies[:mobile_url]= {:value => request.fullpath, :secure => SITE_CONFIG['secure_cookies']}
end
format.xml { render :xml => @projects.all.to_xml( :except => :user_id ) }
format.xml { render :xml => @projects.to_xml( :except => :user_id ) }
format.rss do
@feed_title = I18n.t('models.project.feed_title')
@feed_description = I18n.t('models.project.feed_description', :username => current_user.display_name)
@ -182,7 +181,10 @@ class ProjectsController < ApplicationController
@contexts = current_user.contexts
respond_to do |format|
format.js { @down_count = current_user.projects.size }
format.js do
@down_count = current_user.projects.size
init_not_done_counts
end
format.xml do
if @project.new_record?
render_failure @project.errors.to_xml.html_safe, 409
@ -288,6 +290,7 @@ class ProjectsController < ApplicationController
@projects = current_user.projects.alphabetize(:state => @state) if @state
@contexts = current_user.contexts
init_not_done_counts(['project'])
init_project_hidden_todo_counts(['project']) if @state == 'hidden'
end
def actionize
@ -295,6 +298,7 @@ class ProjectsController < ApplicationController
@projects = current_user.projects.actionize(:state => @state) if @state
@contexts = current_user.contexts
init_not_done_counts(['project'])
init_project_hidden_todo_counts(['project']) if @state == 'hidden'
end
def done_todos

View file

@ -10,26 +10,6 @@ class TodosController < ApplicationController
# :calendar, :auto_complete_for_predecessor, :remove_predecessor, :add_predecessor]
protect_from_forgery :except => :check_deferred
def with_parent_resource_scope(&block)
@feed_title = t('common.actions')
if (params[:context_id])
@context = current_user.contexts.find_by_params(params)
@feed_title = @feed_title + t('todos.feed_title_in_context', :context => @context.name)
Todo.send :where, ['todos.context_id = ?', @context.id] do
yield
end
elsif (params[:project_id])
@project = current_user.projects.find_by_params(params)
@feed_title = @feed_title + t('todos.feed_title_in_project', :project => @project.name)
@project_feed = true
Todo.send :where, ['todos.project_id = ?', @project.id] do
yield
end
else
yield
end
end
def index
@source_view = params['_source_view'] || 'todo'
@ -47,7 +27,11 @@ class TodosController < ApplicationController
@not_done_todos = @not_done_todos.
reorder("todos.due IS NULL, todos.due ASC, todos.created_at ASC").
includes(Todo::DEFAULT_INCLUDES)
@not_done_todos = @not_done_todos.limit(sanitize(params[:limit])) if params[:limit]
if params[:limit]
@not_done_todos = @not_done_todos.limit(sanitize(params[:limit]))
@todos = @todos.limit(sanitize(params[:limit]))
end
if params[:due]
due_within_when = Time.zone.now + params['due'].to_i.days
@ -363,7 +347,7 @@ class TodosController < ApplicationController
def remove_predecessor
@source_view = params['_source_view'] || 'todo'
@todo = current_user.todos.find_by_id(params['id']).includes(Todo::DEFAULT_INCLUDES)
@todo = current_user.todos.includes(Todo::DEFAULT_INCLUDES).find_by_id(params['id'])
@predecessor = current_user.todos.find_by_id(params['predecessor'])
@predecessors = @predecessor.predecessors
@successor = @todo
@ -469,10 +453,10 @@ class TodosController < ApplicationController
end
def change_context
# TODO: is this method used?
@todo = Todo.find_by_id(params[:todo][:id])
# change context if you drag a todo to another context
@todo = current_user.todos.find_by_id(params[:id])
@original_item_context_id = @todo.context_id
@context = Context.find_by_id(params[:todo][:context_id])
@context = current_user.contexts.find_by_id(params[:todo][:context_id])
@todo.context = @context
@saved = @todo.save
@ -1245,7 +1229,7 @@ class TodosController < ApplicationController
end
def update_todo_state_if_project_changed
if ( @project_changed ) then
if @project_changed
@todo.update_state_from_project
@remaining_undone_in_project = current_user.projects.find_by_id(@original_item_project_id).todos.active.count if source_view_is :project
end
@ -1269,8 +1253,8 @@ class TodosController < ApplicationController
end
def update_tags
if params[:todo_tag_list]
@todo.tag_with(params[:todo_tag_list])
if params[:tag_list]
@todo.tag_with(params[:tag_list])
@todo.tags(true) #force a reload for proper rendering
end
end
@ -1423,7 +1407,7 @@ class TodosController < ApplicationController
end
def tag_list
@params['todo_tag_list']
@params['tag_list']
end
def predecessor_list

View file

@ -2,27 +2,12 @@
# application.
module ApplicationHelper
# Replicates the link_to method but also checks request.request_uri to find
# current page. If that matches the url, the link is marked id = "current"
#
def navigation_link(name, options = {}, html_options = nil, *parameters_for_method_reference)
link_to name, options, html_options
# TODO: check if this needs to be converted
# if html_options
# html_options = html_options.stringify_keys
# convert_options_to_javascript!(html_options)
# tag_options = tag_options(html_options)
# else
# tag_options = nil
# end
# url = options.is_a?(String) ? options : self.url_for(options, *parameters_for_method_reference)
# id_tag = (request.request_uri == url) ? " id=\"current\"" : ""
#
# "<a href=\"#{url}\"#{tag_options}#{id_tag}>#{name || url}</a>"
end
def days_from_today(date)
Integer (date.in_time_zone.to_date - current_user.time.to_date)
(date.in_time_zone.to_date - current_user.time.to_date).to_i
end
# Check due date in comparison to today's date Flag up date appropriately with
@ -123,8 +108,7 @@ module ApplicationHelper
end
def link_to_edit_note (note, descriptor = sanitize(note.id.to_s))
link_to(descriptor,
url_for({:controller => 'notes', :action => 'edit', :id => note.id}),
link_to(descriptor, edit_note_path(note),
{:id => "link_edit_#{dom_id(note)}", :class => "note_edit_settings"})
end
@ -133,30 +117,30 @@ module ApplicationHelper
end
def item_link_to_context(item)
descriptor = "[C]"
descriptor = "[#{item.context.name}]" if prefs.verbose_action_descriptors
link_to_context( item.context, descriptor )
link_to_context( item.context, prefs.verbose_action_descriptors ? "[#{item.context.name}]" : "[C]" )
end
def item_link_to_project(item)
descriptor = "[P]"
descriptor = "[#{item.project.name}]" if prefs.verbose_action_descriptors
link_to_project( item.project, descriptor )
link_to_project( item.project, prefs.verbose_action_descriptors ? "[#{item.project.name}]" : "[P]" )
end
def render_flash
render :partial => 'shared/flash', :object => flash
end
def time_span_text(date, i18n_text)
return (date ? "#{i18n_text} #{format_date(date)}" : "").html_safe
end
def recurrence_time_span(rt)
case rt.ends_on
when "no_end_date"
return rt.start_from.nil? ? "" : I18n.t("todos.recurrence.pattern.from") + " " + format_date(rt.start_from)
return time_span_text(rt.start_from, I18n.t("todos.recurrence.pattern.from"))
when "ends_on_number_of_times"
return I18n.t("todos.recurrence.pattern.times", :number => rt.number_of_occurences)
when "ends_on_end_date"
starts = rt.start_from.nil? ? "" : I18n.t("todos.recurrence.pattern.from") + " " + format_date(rt.start_from)
ends = rt.end_date.nil? ? "" : " " + I18n.t("todos.recurrence.pattern.until") + " " + format_date(rt.end_date)
starts = time_span_text(rt.start_from, I18n.t("todos.recurrence.pattern.from"))
ends = time_span_text(rt.end_date, I18n.t("todos.recurrence.pattern.until"))
return starts+ends
else
raise Exception.new, "unknown recurrence time span selection (#{rt.ends_on})"
@ -166,17 +150,15 @@ module ApplicationHelper
def recurrence_pattern_as_text(recurring_todo)
rt = recurring_todo.recurring_target_as_text
rp = recurring_todo.recurrence_pattern
# only add space if recurrence_pattern has content
rp = " " + rp if !rp.nil?
rp = " " + rp unless rp.nil?
rts = recurrence_time_span(recurring_todo)
# only add space if recurrence_time_span has content
rts = " " + rts if !(rts == "")
rts = " " + rts unless rts == ""
return rt+rp+rts
end
def date_format_for_date_picker()
standard_format = current_user.prefs.date_format
translations = [
[
['%m', 'mm'],
['%b', 'M'],
['%B', 'MM'],
@ -185,25 +167,24 @@ module ApplicationHelper
['%A', 'DD'],
['%y', 'y'],
['%Y', 'yy']
]
translations.inject(standard_format) do |str, translation|
str.gsub(*translation)
end
].inject(current_user.prefs.date_format) { |str, translation| str.gsub(*translation) }
end
def sidebar_html_for_titled_list (list, title)
return content_tag(:h3, title+" (#{list.size})") + content_tag(:ul, sidebar_html_for_list(list))
end
def link_to_sidebar_item(item)
item.is_a?(Project) ? link_to_project( item ) : link_to_context( item )
end
def sidebar_html_for_item(item)
content_tag(:li, link_to_sidebar_item(item) + " (" + count_undone_todos_phrase(item)+")")
end
def sidebar_html_for_list(list)
if list.empty?
return content_tag(:li, t('sidebar.list_empty')).html_safe
else
return list.inject("") do |html, item|
link = (item.class == "Project") ? link_to_project( item ) : link_to_context(item)
html << content_tag(:li, link + " (" + count_undone_todos_phrase(item)+")")
end.html_safe
end
return content_tag(:li, t('sidebar.list_empty')).html_safe if list.empty?
return list.inject("") { |html, item| html << sidebar_html_for_item(item) }.html_safe
end
def generate_i18n_strings
@ -230,7 +211,7 @@ module ApplicationHelper
def javascript_tag_for_i18n_datepicker
locale = I18n.locale
# do not include en as locale since this the available by default
if locale and locale != :en
if locale && locale != :en
javascript_include_tag("i18n/jquery.ui.datepicker-#{locale}.js")
end
end

View file

@ -1,14 +1,5 @@
module ContextsHelper
def get_listing_sortable_options
{
:tag => 'div',
:handle => 'handle',
:complete => visual_effect(:highlight, 'list-contexts'),
:url => order_contexts_path
}
end
def link_to_delete_context(context, descriptor = sanitize(context.name))
link_to(descriptor,
context_path(context, :format => 'js'),
@ -21,8 +12,7 @@ module ContextsHelper
end
def link_to_edit_context (context, descriptor = sanitize(context.name))
link_to(descriptor,
url_for({:controller => 'contexts', :action => 'edit', :id => context.id}),
link_to(descriptor, edit_context_path(context),
{
:id => "link_edit_#{dom_id(context)}",
:class => "context_edit_settings icon"

View file

@ -1,25 +1,26 @@
module FeedlistHelper
def linkoptions(format, options)
merge_hashes( {:format => format}, options, user_token_hash)
end
def rss_formatted_link(options = {})
image_tag = image_tag("feed-icon.png", :size => "16X16", :border => 0, :class => "rss-icon")
linkoptions = merge_hashes( {:format => 'rss'}, user_token_hash, options)
link_to(image_tag, linkoptions, :title => "RSS feed")
link_to(image_tag, linkoptions('rss', options), :title => "RSS feed")
end
def text_formatted_link(options = {})
linkoptions = merge_hashes( {:format => 'txt'}, user_token_hash, options)
link_to(content_tag(:span, 'TXT', {:class => 'feed', :title => "Plain text feed"}), linkoptions)
link_to(content_tag(:span, 'TXT', {:class => 'feed', :title => "Plain text feed"}), linkoptions('txt', options))
end
def ical_formatted_link(options = {})
linkoptions = merge_hashes( {:format => 'ics'}, user_token_hash, options)
link_to(content_tag(:span, 'iCal', {:class=>"feed", :title => "iCal feed"}), linkoptions)
link_to(content_tag(:span, 'iCal', {:class=>"feed", :title => "iCal feed"}), linkoptions('ics', options))
end
def feed_links(feeds, link_options, title)
space = " "
html = ""
html << rss_formatted_link(link_options)+space if feeds.include?(:rss)
html << rss_formatted_link(link_options) +space if feeds.include?(:rss)
html << text_formatted_link(link_options)+space if feeds.include?(:txt)
html << ical_formatted_link(link_options)+space if feeds.include?(:ical)
html << title

View file

@ -1,47 +1,18 @@
module ProjectsHelper
def get_listing_sortable_options(list_container_id)
{
:tag => 'div',
:handle => 'handle',
:complete => visual_effect(:highlight, list_container_id),
:url => order_projects_path
}
end
def set_element_visible(id,test)
if (test)
page.show id
else
page.hide id
end
end
def project_next_prev
html = ""
if @previous_project
project_name = truncate(@previous_project.name, :length => 40, :omission => "...")
html << link_to_project(@previous_project, "&laquo; #{project_name}".html_safe)
end
html << link_to_project(@previous_project, "&laquo; #{@previous_project.shortened_name}".html_safe) if @previous_project
html << " | " if @previous_project && @next_project
if @next_project
project_name = truncate(@next_project.name, :length => 40, :omission => "...")
html << link_to_project(@next_project, "#{project_name} &raquo;".html_safe)
end
html.html_safe
html << link_to_project(@next_project, "#{@next_project.shortened_name} &raquo;".html_safe) if @next_project
return html.html_safe
end
def project_next_prev_mobile
prev_project,next_project= "", ""
unless @previous_project.nil?
project_name = truncate(@previous_project.name, :length => 40, :omission => "...")
prev_project = content_tag(:li, link_to_project_mobile(@previous_project, "5", project_name), :class=>"prev")
end
unless @next_project.nil?
project_name = truncate(@next_project.name, :length => 40, :omission => "...")
next_project = content_tag(:li, link_to_project_mobile(@next_project, "6", project_name), :class=>"next")
end
return content_tag(:ul, "#{prev_project}#{next_project}".html_safe, :class=>"next-prev-project").html_safe
prev_project = content_tag(:li, link_to_project_mobile(@previous_project, "5", @previous_project.shortened_name), :class=>"prev") if @previous_project
next_project = content_tag(:li, link_to_project_mobile(@next_project, "6", @next_project.shortened_name), :class=>"next") if @next_project
return content_tag(:ul, "#{prev_project}#{next_project}".html_safe, :class=>"next-prev-project")
end
def link_to_delete_project(project, descriptor = sanitize(project.name))
@ -58,8 +29,7 @@ module ProjectsHelper
end
def link_to_edit_project (project, descriptor = sanitize(project.name))
link_to(descriptor,
url_for({:controller => 'projects', :action => 'edit', :id => project.id}),
link_to(descriptor, edit_project_path(project),
{
:id => "link_edit_#{dom_id(project)}",
:class => "project_edit_settings icon"
@ -72,7 +42,6 @@ module ProjectsHelper
project_description += content_tag(:p,
"#{count_undone_todos_phrase(p)}. #{t('projects.project_state', :state => project.state)}".html_safe
)
raw project_description
end
def needsreview_class(item)

View file

@ -16,8 +16,12 @@ module TodosHelper
end
def remote_delete_menu_item(todo)
# TODO: what is the current way to do mouseover with css?
return link_to(
image_tag("delete_off.png", :mouseover => "delete_on.png", :alt => t('todos.delete'), :align => "absmiddle")+" "+t('todos.delete'),
image_tag("delete_off.png",
:onmouseover => "this.src='#{path_to_image("delete_on.png")}'",
:onmouseout => "this.src='#{path_to_image("delete_off.png")}'",
:alt => t('todos.delete'), :align => "absmiddle")+" "+t('todos.delete'),
{:controller => 'todos', :action => 'destroy', :id => todo.id},
:class => "icon_delete_item",
:id => "delete_#{dom_id(todo)}",
@ -59,7 +63,11 @@ module TodosHelper
end
def image_tag_for_defer(days)
image_tag("defer_#{days}_off.png", :mouseover => "defer_#{days}.png", :alt => t('todos.defer_x_days', :count => days), :align => "absmiddle")+" "+t('todos.defer_x_days', :count => days)
# TODO: what is the current way to do mouseover with css?
image_tag("defer_#{days}_off.png",
:onmouseover => "this.src='#{path_to_image("defer_#{days}.png")}'",
:onmouseout => "this.src='#{path_to_image("defer_#{days}_off.png")}'",
:alt => t('todos.defer_x_days', :count => days), :align => "absmiddle")+" "+t('todos.defer_x_days', :count => days)
end
def collapsed_notes_image(todo)
@ -135,7 +143,7 @@ module TodosHelper
end
def tag_span (tag, mobile=false)
content_tag(:span, :class => "tag #{tag.name.gsub(' ','-')}") { link_to(tag.name, tag_path(tag.name, :format => mobile ? :m : :html)) }
content_tag(:span, :class => "tag #{tag.name.gsub(' ','-')}") { link_to(tag.name, tag_path(tag.name, :format => mobile ? :m : nil)) }
end
def tag_list(todo=@todo, mobile=false)
@ -162,7 +170,7 @@ module TodosHelper
if (['project', 'tag', 'stats', 'search'].include?(parent_container_type))
str << item_link_to_context( todo )
end
if (['context', 'tickler', 'tag', 'stats', 'search'].include?(parent_container_type)) && todo.project_id
if (['context', 'tickler', 'tag', 'stats', 'search'].include?(parent_container_type)) && !todo.project_id.nil? && !todo.project.is_a?(NullProject)
str << item_link_to_project( todo )
end
end

View file

@ -4,7 +4,7 @@ class MessageGateway < ActionMailer::Base
def receive(email)
user = get_user_from_email_address(email)
return if user.nil?
return false if user.nil?
context = user.prefs.sms_context
description = nil
@ -23,11 +23,9 @@ class MessageGateway < ActionMailer::Base
end
end
# stupid T-Mobile often sends the same message multiple times
return if user.todos.where(:description => description).first
todo = Todo.from_rich_message(user, context.id, description, notes)
todo.save!
Rails.logger.info "Saved email as todo for user #{user.login} in context #{context.name}"
end
private
@ -36,14 +34,27 @@ class MessageGateway < ActionMailer::Base
return SITE_CONFIG['email_dispatch'] == 'to' ? email.to[0] : email.from[0]
end
def get_user_from_email_address(email)
def get_user_from_env_setting
Rails.logger.info "All received email goes to #{ENV['TRACKS_MAIL_RECEIVER']}"
user = User.find_by_login(ENV['TRACKS_MAIL_RECEIVER'])
Rails.logger.info "WARNING: Unknown user set for TRACKS_MAIL_RECEIVER (#{ENV['TRACKS_MAIL_RECEIVER']})" if user.nil?
return user
end
def get_user_from_mail_header(email)
address = get_address(email)
Rails.logger.info "Looking for user with email #{address}"
user = User.where("preferences.sms_email" => address.strip).includes(:preference).first
if user.nil?
user = User.where("preferences.sms_email" => address.strip[1.100]).includes(:preference).first
end
Rails.logger.info(!user.nil? ? "Email belongs to #{user.login}" : "User unknown")
return user
end
def get_user_from_email_address(email)
SITE_CONFIG['email_dispatch'] == 'single_user' ? get_user_from_env_setting : get_user_from_mail_header(email)
end
def get_text_or_nil(text)
return text ? sanitize(text.strip) : nil

View file

@ -1,5 +1,5 @@
class Project < ActiveRecord::Base
has_many :todos, :dependent => :delete_all
has_many :todos, :dependent => :delete_all, :order => 'todos.due IS NULL, todos.due ASC, todos.created_at ASC'
has_many :notes, :dependent => :delete_all, :order => "created_at DESC"
has_many :recurring_todos
@ -118,6 +118,9 @@ class Project < ActiveRecord::Base
return self.todos.deferred_or_blocked.empty? && self.todos.not_deferred_or_blocked.empty?
end
def shortened_name(length=40)
name.truncate(length, :omission => "...").html_safe
end
def name=(value)
self[:name] = value.gsub(/\s{2,}/, " ").strip

View file

@ -1,17 +1,16 @@
class RecurringTodo < ActiveRecord::Base
attr_protected :user
belongs_to :context
belongs_to :project
belongs_to :user
has_many :todos
include IsTaggable
scope :active, :conditions => { :state => 'active'}
scope :completed, :conditions => { :state => 'completed'}
attr_protected :user
include IsTaggable
include AASM
aasm_column :state
@ -50,31 +49,29 @@ class RecurringTodo < ActiveRecord::Base
end
def validate_daily
if (!only_work_days) && (daily_every_x_days.nil? || daily_every_x_days.blank?)
if (!only_work_days) && daily_every_x_days.blank?
errors[:base] << "Every other nth day may not be empty for recurrence setting"
end
end
def validate_weekly
if weekly_every_x_week.nil? || weekly_every_x_week.blank?
if weekly_every_x_week.blank?
errors[:base] << "Every other nth week may not be empty for recurrence setting"
end
something_set = false
%w{sunday monday tuesday wednesday thursday friday saturday}.each do |day|
something_set ||= self.send("on_#{day}")
end
errors[:base] << "You must specify at least one day on which the todo recurs" if !something_set
%w{sunday monday tuesday wednesday thursday friday saturday}.each { |day| something_set ||= self.send("on_#{day}") }
errors[:base] << "You must specify at least one day on which the todo recurs" unless something_set
end
def validate_monthly
case recurrence_selector
when 0 # 'monthly_every_x_day'
errors[:base] << "The day of the month may not be empty for recurrence setting" if monthly_every_x_day.nil? || monthly_every_x_day.blank?
errors[:base] << "Every other nth month may not be empty for recurrence setting" if monthly_every_x_month.nil? || monthly_every_x_month.blank?
errors[:base] << "The day of the month may not be empty for recurrence setting" if monthly_every_x_day.blank?
errors[:base] << "Every other nth month may not be empty for recurrence setting" if monthly_every_x_month.blank?
when 1 # 'monthly_every_xth_day'
errors[:base] <<"Every other nth month may not be empty for recurrence setting" if monthly_every_x_month2.nil? || monthly_every_x_month2.blank?
errors[:base] <<"The nth day of the month may not be empty for recurrence setting" if monthly_every_xth_day.nil? || monthly_every_xth_day.blank?
errors[:base] <<"The day of the month may not be empty for recurrence setting" if monthly_day_of_week.nil? || monthly_day_of_week.blank?
errors[:base] <<"Every other nth month may not be empty for recurrence setting" if monthly_every_x_month2.blank?
errors[:base] <<"The nth day of the month may not be empty for recurrence setting" if monthly_every_xth_day.blank?
errors[:base] <<"The day of the month may not be empty for recurrence setting" if monthly_day_of_week.blank?
else
raise Exception.new, "unexpected value of recurrence selector '#{self.recurrence_selector}'"
end
@ -83,24 +80,24 @@ class RecurringTodo < ActiveRecord::Base
def validate_yearly
case recurrence_selector
when 0 # 'yearly_every_x_day'
errors[:base] << "The month of the year may not be empty for recurrence setting" if yearly_month_of_year.nil? || yearly_month_of_year.blank?
errors[:base] << "The day of the month may not be empty for recurrence setting" if yearly_every_x_day.nil? || yearly_every_x_day.blank?
errors[:base] << "The month of the year may not be empty for recurrence setting" if yearly_month_of_year.blank?
errors[:base] << "The day of the month may not be empty for recurrence setting" if yearly_every_x_day.blank?
when 1 # 'yearly_every_xth_day'
errors[:base] << "The month of the year may not be empty for recurrence setting" if yearly_month_of_year2.nil? || yearly_month_of_year2.blank?
errors[:base] << "The nth day of the month may not be empty for recurrence setting" if yearly_every_xth_day.nil? || yearly_every_xth_day.blank?
errors[:base] << "The day of the week may not be empty for recurrence setting" if yearly_day_of_week.nil? || yearly_day_of_week.blank?
errors[:base] << "The month of the year may not be empty for recurrence setting" if yearly_month_of_year2.blank?
errors[:base] << "The nth day of the month may not be empty for recurrence setting" if yearly_every_xth_day.blank?
errors[:base] << "The day of the week may not be empty for recurrence setting" if yearly_day_of_week.blank?
else
raise Exception.new, "unexpected value of recurrence selector '#{self.recurrence_selector}'"
end
end
def starts_and_ends_on_validations
errors[:base] << "The start date needs to be filled in" if start_from.nil? || start_from.blank?
errors[:base] << "The start date needs to be filled in" if start_from.blank?
case self.ends_on
when 'ends_on_number_of_times'
errors[:base] << "The number of recurrences needs to be filled in for 'Ends on'" if number_of_occurences.nil? || number_of_occurences.blank?
errors[:base] << "The number of recurrences needs to be filled in for 'Ends on'" if number_of_occurences.blank?
when "ends_on_end_date"
errors[:base] << "The end date needs to be filled in for 'Ends on'" if end_date.nil? || end_date.blank?
errors[:base] << "The end date needs to be filled in for 'Ends on'" if end_date.blank?
else
errors[:base] << "The end of the recurrence is not selected" unless ends_on == "no_end_date"
end
@ -114,7 +111,7 @@ class RecurringTodo < ActiveRecord::Base
when 'due_date'
errors[:base] << "Please select when to show the action" if show_always.nil?
unless show_always
errors[:base] << "Please fill in the number of days to show the todo before the due date" if show_from_delta.nil? || show_from_delta.blank?
errors[:base] << "Please fill in the number of days to show the todo before the due date" if show_from_delta.blank?
end
else
raise Exception.new, "unexpected value of recurrence target selector '#{self.recurrence_target}'"
@ -157,9 +154,7 @@ class RecurringTodo < ActiveRecord::Base
end
def daily_every_x_days=(x)
if recurring_period=='daily'
self.every_other1 = x
end
self.every_other1 = x if recurring_period=='daily'
end
def daily_every_x_days
@ -177,9 +172,7 @@ class RecurringTodo < ActiveRecord::Base
end
def switch_week_day (day, position)
if self.every_day.nil?
self.every_day=' '
end
self.every_day = ' ' if self.every_day.nil?
self.every_day = self.every_day[0,position] + day + self.every_day[position+1,self.every_day.length]
end
@ -250,9 +243,7 @@ class RecurringTodo < ActiveRecord::Base
# MONTHLY
def monthly_selector=(selector)
if recurring_period=='monthly'
self.recurrence_selector= (selector=='monthly_every_x_day')? 0 : 1
end
self.recurrence_selector = ( (selector=='monthly_every_x_day') ? 0 : 1) if recurring_period=='monthly'
end
def monthly_every_x_day=(x)
@ -280,11 +271,7 @@ class RecurringTodo < ActiveRecord::Base
def monthly_every_x_month
# in case monthly pattern is every day x, return every_other2 otherwise
# return a default value
if self.recurrence_selector == 0
return self.every_other2
else
return 1
end
return self.recurrence_selector == 0 ? self.every_other2 : 1
end
def monthly_every_x_month2=(x)
@ -294,11 +281,7 @@ class RecurringTodo < ActiveRecord::Base
def monthly_every_x_month2
# in case monthly pattern is every xth day, return every_other2 otherwise
# return a default value
if self.recurrence_selector == 1
return self.every_other2
else
return 1
end
return self.recurrence_selector == 1 ? self.every_other2 : 1
end
def monthly_every_xth_day=(x)
@ -321,9 +304,7 @@ class RecurringTodo < ActiveRecord::Base
# YEARLY
def yearly_selector=(selector)
if recurring_period=='yearly'
self.recurrence_selector = (selector=='yearly_every_x_day') ? 0 : 1
end
self.recurrence_selector = ( (selector=='yearly_every_x_day') ? 0 : 1) if recurring_period=='yearly'
end
def yearly_month_of_year=(moy)
@ -333,11 +314,7 @@ class RecurringTodo < ActiveRecord::Base
def yearly_month_of_year
# if recurrence pattern is every x day in a month, return month otherwise
# return a default value
if self.recurrence_selector == 0
return self.every_other2
else
return Time.zone.now.month
end
return self.recurrence_selector == 0 ? self.every_other2 : Time.zone.now.month
end
def yearly_month_of_year2=(moy)
@ -347,11 +324,7 @@ class RecurringTodo < ActiveRecord::Base
def yearly_month_of_year2
# if recurrence pattern is every xth day in a month, return month otherwise
# return a default value
if self.recurrence_selector == 1
return self.every_other2
else
return Time.zone.now.month
end
return self.recurrence_selector == 1 ? self.every_other2 : Time.zone.now.month
end
def yearly_every_x_day=(x)
@ -403,51 +376,60 @@ class RecurringTodo < ActiveRecord::Base
self.show_always=value
end
def daily_recurrence_pattern
return I18n.t("todos.recurrence.pattern.on_work_days") if only_work_days
if every_other1 > 1
return I18n.t("todos.recurrence.pattern.every_n", :n => every_other1) + " " + I18n.t("common.days_midsentence.other")
else
return I18n.t("todos.recurrence.pattern.every_day")
end
end
def weekly_recurrence_pattern
if every_other1 > 1
return I18n.t("todos.recurrence.pattern.every_n", :n => every_other1) + " " + I18n.t("common.weeks")
else
return I18n.t('todos.recurrence.pattern.weekly')
end
end
def monthly_recurrence_pattern
return "invalid repeat pattern" if every_other2.nil?
if self.recurrence_selector == 0
on_day = " #{I18n.t('todos.recurrence.pattern.on_day_n', :n => self.every_other1)}"
if self.every_other2>1
return I18n.t("todos.recurrence.pattern.every_n", :n => self.every_other2) + " " + I18n.t('common.months') + on_day
else
return I18n.t("todos.recurrence.pattern.every_month") + on_day
end
else
if self.every_other2>1
n_months = "#{self.every_other2} #{I18n.t('common.months')}"
else
n_months = I18n.t('common.month')
end
return I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months',
:x => self.xth, :day => self.day_of_week, :n_months => n_months)
end
end
def yearly_recurrence_pattern
if self.recurrence_selector == 0
return I18n.t("todos.recurrence.pattern.every_year_on",
:date => I18n.l(DateTime.new(Time.zone.now.year, self.every_other2, self.every_other1), :format => :month_day))
else
return I18n.t("todos.recurrence.pattern.every_year_on",
:date => I18n.t("todos.recurrence.pattern.the_xth_day_of_month", :x => self.xth, :day => self.day_of_week, :month => self.month_of_year))
end
end
def recurrence_pattern
return "invalid repeat pattern" if every_other1.nil?
case recurring_period
when 'daily'
if only_work_days
return I18n.t("todos.recurrence.pattern.on_work_days")
else
if every_other1 > 1
return I18n.t("todos.recurrence.pattern.every_n", :n => every_other1) + " " + I18n.t("common.days")
else
return I18n.t("todos.recurrence.pattern.every_day")
end
end
when 'weekly'
if every_other1 > 1
return I18n.t("todos.recurrence.pattern.every_n", :n => every_other1) + " " + I18n.t("common.weeks")
else
return I18n.t('todos.recurrence.pattern.weekly')
end
when 'monthly'
return "invalid repeat pattern" if every_other2.nil?
if self.recurrence_selector == 0
on_day = " " + I18n.t('todos.recurrence.pattern.on_day_n', :n => self.every_other1)
if self.every_other2>1
return I18n.t("todos.recurrence.pattern.every_n", :n => self.every_other2) + " " + I18n.t('common.months') + on_day
else
return I18n.t("todos.recurrence.pattern.every_month") + on_day
end
else
if self.every_other2>1
n_months = "#{self.every_other2} #{I18n.t('common.months')}"
else
n_months = I18n.t('common.month')
end
return I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months',
:x => self.xth, :day => self.day_of_week, :n_months => n_months)
end
when 'yearly'
if self.recurrence_selector == 0
return I18n.t("todos.recurrence.pattern.every_year_on",
:date => I18n.l(DateTime.new(Time.zone.now.year, self.every_other2, self.every_other1), :format => :month_day))
else
return I18n.t("todos.recurrence.pattern.every_year_on",
:date => I18n.t("todos.recurrence.pattern.the_xth_day_of_month", :x => self.xth, :day => self.day_of_week, :month => self.month_of_year))
end
when 'daily' then daily_recurrence_pattern
when 'weekly' then weekly_recurrence_pattern
when 'monthly' then monthly_recurrence_pattern
when 'yearly' then yearly_recurrence_pattern
else
return 'unknown recurrence pattern: period unknown'
end
@ -461,7 +443,7 @@ class RecurringTodo < ActiveRecord::Base
end
def day_of_week
return (self.every_count.nil? ? '??' : I18n.t('todos.recurrence.pattern.day_names')[self.every_count])
return self.every_count.nil? ? '??' : I18n.t('todos.recurrence.pattern.day_names')[self.every_count]
end
def month_of_year
@ -472,10 +454,6 @@ class RecurringTodo < ActiveRecord::Base
return has_tag?(Todo::STARRED_TAG_NAME)
end
def has_tag?(tag_name)
return self.tags.any? {|tag| tag.name == tag_name}
end
def get_due_date(previous)
case self.target
when 'due_date'
@ -492,14 +470,10 @@ class RecurringTodo < ActiveRecord::Base
case self.target
when 'due_date'
# so set show from date relative to due date unless show_always is true or show_from_delta is nil
if self.show_always? or self.show_from_delta.nil?
nil
else
get_due_date(previous) - self.show_from_delta.days
end
(self.show_always? || self.show_from_delta.nil?) ? nil : get_due_date(previous) - self.show_from_delta.days
when 'show_from_date'
# Leave due date empty
return get_next_date(previous)
get_next_date(previous)
else
raise Exception.new, "unexpected value of recurrence target '#{self.target}'"
end
@ -507,14 +481,10 @@ class RecurringTodo < ActiveRecord::Base
def get_next_date(previous)
case self.recurring_period
when 'daily'
return get_daily_date(previous)
when 'weekly'
return get_weekly_date(previous)
when 'monthly'
return get_monthly_date(previous)
when 'yearly'
return get_yearly_date(previous)
when 'daily' then get_daily_date(previous)
when 'weekly' then get_weekly_date(previous)
when 'monthly' then get_monthly_date(previous)
when 'yearly' then get_yearly_date(previous)
else
raise Exception.new, "unknown recurrence pattern: '#{self.recurring_period}'"
end
@ -689,21 +659,16 @@ class RecurringTodo < ActiveRecord::Base
end
def has_next_todo(previous)
unless self.number_of_occurences.nil?
return self.occurences_count < self.number_of_occurences
return self.occurences_count < self.number_of_occurences unless self.number_of_occurences.nil?
return true if self.end_date.nil? || self.ends_on == 'no_end_date'
case self.target
when 'due_date'
return get_due_date(previous) <= self.end_date
when 'show_from_date'
return get_show_from_date(previous) <= self.end_date
else
if self.end_date.nil? || self.ends_on == 'no_end_date'
return true
else
case self.target
when 'due_date'
return get_due_date(previous) <= self.end_date
when 'show_from_date'
return get_show_from_date(previous) <= self.end_date
else
raise Exception.new, "unexpected value of recurrence target '#{self.target}'"
end
end
raise Exception.new, "unexpected value of recurrence target '#{self.target}'"
end
end
@ -713,12 +678,11 @@ class RecurringTodo < ActiveRecord::Base
def toggle_star!
if starred?
_remove_tags Todo::STARRED_TAG_NAME
tags.reload
_remove_tags(Todo::STARRED_TAG_NAME)
else
_add_tags(Todo::STARRED_TAG_NAME)
tags.reload
end
tags.reload
starred?
end

View file

@ -26,12 +26,12 @@ class Todo < ActiveRecord::Base
scope :active, :conditions => { :state => 'active' }
scope :active_or_hidden, :conditions => ["todos.state = ? OR todos.state = ?", 'active', 'project_hidden']
scope :not_completed, :conditions => ['NOT (todos.state = ?)', 'completed']
scope :completed, :conditions => ["NOT (todos.completed_at IS NULL)"]
scope :deferred, :conditions => ["todos.completed_at IS NULL AND NOT (todos.show_from IS NULL)"]
scope :completed, :conditions => ["todos.state = ?", 'completed']
scope :deferred, :conditions => ["todos.state = ?", 'deferred']
scope :blocked, :conditions => ['todos.state = ?', 'pending']
scope :pending, :conditions => ['todos.state = ?', 'pending']
scope :deferred_or_blocked, :conditions => ["(todos.completed_at IS NULL AND NOT(todos.show_from IS NULL)) OR (todos.state = ?)", "pending"]
scope :not_deferred_or_blocked, :conditions => ["(todos.completed_at IS NULL) AND (todos.show_from IS NULL) AND (NOT todos.state = ?)", "pending"]
scope :deferred_or_blocked, :conditions => ["(todos.state = ?) OR (todos.state = ?)", "deferred", "pending"]
scope :not_deferred_or_blocked, :conditions => ["(NOT todos.state=?) AND (NOT todos.state = ?)", "deferred", "pending"]
scope :hidden,
:joins => "INNER JOIN contexts c_hidden ON c_hidden.id = todos.context_id",
:conditions => ["todos.state = ? OR (c_hidden.hide = ? AND (todos.state = ? OR todos.state = ? OR todos.state = ?))",
@ -204,23 +204,19 @@ class Todo < ActiveRecord::Base
return !pending_successors.empty?
end
def has_tag?(tag_name)
return self.tags.any? {|tag| tag.name == tag_name}
end
def hidden?
return self.state == 'project_hidden' || ( self.context.hidden? && (self.state == 'active' || self.state == 'deferred'))
end
def update_state_from_project
if self.state == 'project_hidden' and !self.project.hidden?
if self.project_hidden? && (!self.project.hidden?)
if self.uncompleted_predecessors.empty?
self.state = 'active'
self.activate!
else
self.state = 'pending'
self.block!
end
elsif self.state == 'active' and self.project.hidden?
self.state = 'project_hidden'
elsif self.active? && self.project.hidden?
self.hide!
end
self.save!
end

View file

@ -17,20 +17,20 @@
</tr>
<tr>
<td>YAML file containing all your actions, contexts, projects, tags and notes</td>
<td><%= link_to "YAML file", :controller => 'data', :action => 'yaml_export' %></td>
<td><%= link_to "YAML file", data_yaml_export_path %></td>
</tr>
<tr>
<td>CSV file containing all of your actions, with named contexts and projects</td>
<td><%= link_to "CSV file (actions, contexts and projects)", :controller => 'data', :action => 'csv_actions' %></td>
<td><%= link_to "CSV file (actions, contexts and projects)", data_csv_actions_path %></td>
</tr>
<tr>
<td>CSV file containing all your notes</td>
<td><%= link_to "CSV file (notes only)", :controller => 'data', :action => 'csv_notes' %></td>
<td><%= link_to "CSV file (notes only)", data_csv_notes_path %></td>
</tr>
<tr>
<td>XML file containing all your actions, contexts, projects, tags and notes</td>
<td><%= link_to "XML file (actions only)", :controller => 'data', :action => 'xml_export' %></td>
<td><%= link_to "XML file (actions only)", data_xml_export_path %></td>
</tr>
</table>
</table>
</div><!-- End of feeds -->
</div>

View file

@ -100,18 +100,24 @@
If Tracks is running on the same server as your mail server, you can use the integrated mail handler built into tracks. Steps to set it up:
</p>
<ul>
<li>Go to <%= link_to t('layouts.navigation.preferences'), preferences_url %>
and set your "<%= Preference.human_attribute_name('sms_email') %>" and
<li>Go to <%= link_to t('layouts.navigation.preferences'), preferences_url %> and
set your "<%= Preference.human_attribute_name('sms_email') %>" and
"<%= Preference.human_attribute_name('sms_context') %>" for todos sent in
via email (which could come from an SMS message)</li>
<li>In sendmail/qmail/postfix/whatever, set up an email address alias
to pipe messages to <pre>/PATH/TO/RUBY/ruby /PATH/TO/TRACKS/script/runner -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
<li>In sendmail/qmail/postfix/whatever, set up an email address
alias to pipe messages to <pre>/usr/bin/bundle exec /PATH/TO/TRACKS/script/rails r -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
<li>Send an email to your newly configured address!</li>
</ul>
<p>You can also use the Rich Todo API to send in tasks like "do laundry @ Home"
or "Call Bill > project X". The subject of the message will fill description,
context, and project, while the body will populate the tasks's note.
</p>
<p>
You may need to configure your <tt>site.yml</tt> to tell the message gateway to look at the <tt>to:</tt> field or <tt>from:</tt> field to
lookup Tracks' user from the email address in that field. </p>
<p>You can also send all email to a specific Tracks user. Configure mail_dispatch in site.yml to <tt>single_user</tt> and pass the login of the user:
<pre>TRACKS_MAIL_RECEIVER=<%=current_user.login%> usr/bin/bundle exec /PATH/TO/TRACKS/script/rails r -e production 'MessageGateway.receive(STDIN.read)'</pre>
</p>
<a name="google_gadget"> </a>
<h2>Add Tracks as a Google Gmail gadget</h2>

View file

@ -105,13 +105,19 @@
"<%= Preference.human_attribute_name('sms_context') %>" for todos sent in
via email (which could come from an SMS message)</li>
<li>In sendmail/qmail/postfix/whatever, set up an email address
alias to pipe messages to <pre>/PATH/TO/RUBY/ruby /PATH/TO/TRACKS/script/runner -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
alias to pipe messages to <pre>/usr/bin/bundle exec /PATH/TO/TRACKS/script/rails r -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
<li>Send an email to your newly configured address!</li>
</ul>
<p>You can also use the Rich Todo API to send in tasks like "do laundry @ Home"
or "Call Bill > project X". The subject of the message will fill description,
context, and project, while the body will populate the tasks's note.
</p>
<p>
You may need to configure your <tt>site.yml</tt> to tell the message gateway to look at the <tt>to:</tt> field or <tt>from:</tt> field to
lookup Tracks' user from the email address in that field. </p>
<p>You can also send all email to a specific Tracks user. Configure mail_dispatch in site.yml to <tt>single_user</tt> and pass the login of the user:
<pre>TRACKS_MAIL_RECEIVER=<%=current_user.login%> usr/bin/bundle exec /PATH/TO/TRACKS/script/rails r -e production 'MessageGateway.receive(STDIN.read)'</pre>
</p>
<a name="google_gadget"> </a>
<h2>Add Tracks as a Google Gmail gadget</h2>

View file

@ -109,13 +109,19 @@
verzonden zijn via email (die bijv. komen via een SMS message)</li>
<li>In sendmail/qmail/postfix/whatever, stel een email address
alias in om berichten door te sturen naar
<pre>/PATH/TO/RUBY/ruby /PATH/TO/TRACKS/script/runner -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
<pre>/usr/bin/bundle exec /PATH/TO/TRACKS/script/rails r -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
<li>Verstuur een email naar het net geconfigureerde e-mail adres!</li>
</ul>
<p>Je kan ook de Rich Todo API gebruiken om acties te maken zoals "do laundry @ Home"
of "Call Bill > project X". Het onderwerp van het bericht zal de bijschrijving van de actie vullen,
de context, en het project, terwijl de body van het bericht de notities van de actie zal vullen.
</p>
<p>
Het kan nodig zijn om jouw <tt>site.yml</tt> te configureren om de message gateway te laten kijken naar het <tt>to:</tt> veld of het <tt>from:</tt> veld om
de Tracks-gebruiker op te zoeken met het emailadres uit dat veld. </p>
<p>Je kan ook alle email naar een specifieke Tracks gebruiker sturen. Stel mail_dispatch in site.yml in op <tt>single_user</tt> en geeft de login van de gebruiker door:
<pre>TRACKS_MAIL_RECEIVER=<%=current_user.login%> usr/bin/bundle exec /PATH/TO/TRACKS/script/rails r -e production 'MessageGateway.receive(STDIN.read)'</pre>
</p>
<a name="google_gadget"> </a>
<h2>Voeg tracks toe als een Google Gmail gadget</h2>

View file

@ -8,7 +8,7 @@
<%= csrf_meta_tag %>
<script type="text/javascript">
var SOURCE_VIEW = '<%=@source_view%>';
var AUTH_TOKEN = '<%= protect_against_forgery? ? form_authenticity_token.inspect : "" %>'
var AUTH_TOKEN = '<%= raw(protect_against_forgery? ? form_authenticity_token.inspect : "") %>'
var TAG_NAME = '<%= @tag_name ? @tag_name : "" %>'
var defaultContexts = <%= default_contexts_for_autocomplete.html_safe rescue '{}' %>;
var defaultTags = <%= default_tags_for_autocomplete.html_safe rescue '{}' %>;
@ -26,7 +26,7 @@
</script>
<link rel="shortcut icon" href="<%= image_path ('favicon.ico') %>" />
<%= auto_discovery_link_tag(:rss, {:controller => "todos", :action => "index", :format => 'rss', :token => "#{current_user.token}"}, {:title => t('layouts.next_actions_rss_feed')}) %>
<link rel="search" type="application/opensearchdescription+xml" title="Tracks" href="<%= search_plugin_path %>" />
<link rel="search" type="application/opensearchdescription+xml" title="Tracks" href="<%= search_plugin_path(:format => :xml) %>" />
<title><%= @page_title %></title>
</head>

View file

@ -10,7 +10,7 @@
t('preferences.generate_new_token'),
refresh_token_user_path( current_user ),
:method => :post,
:confirm => t('preferences.generate_new_token_confirm'),
:data => {:confirm => t('preferences.generate_new_token_confirm')},
:id=>'prefs_new_token') %>
</div>

View file

@ -35,17 +35,17 @@
<br/>
<label for="recurring_todo[ends_on]"><%= t('todos.recurrence.ends_on') %>:</label><br/>
<%= radio_button_tag('recurring_todo[ends_on]', 'no_end_date', true, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.no_end_date') %><br/>
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_number_of_times', false, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.ends_on_number_times', :number => text_field( :recurring_todo, :number_of_occurences, "size" => 3, "tabindex" => next_tab_index)) %> <br/>
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_end_date', false, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.ends_on_date', :date => text_field(:recurring_todo, :end_date, "size" => 12, "class" => "Date", "tabindex" => next_tab_index, "autocomplete" => "off", "value" => "")) %><br/>
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_number_of_times', false, {:tabindex => next_tab_index})%> <%= raw t('todos.recurrence.ends_on_number_times', :number => text_field( :recurring_todo, :number_of_occurences, "size" => 3, "tabindex" => next_tab_index)) %> <br/>
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_end_date', false, {:tabindex => next_tab_index})%> <%= raw t('todos.recurrence.ends_on_date', :date => text_field(:recurring_todo, :end_date, "size" => 12, "class" => "Date", "tabindex" => next_tab_index, "autocomplete" => "off", "value" => "")) %><br/>
</div></div>
<div id="recurring_daily" style="display:block">
<label><%= t('todos.recurrence.daily_options') %></label><br/>
<%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_x_day', true, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.daily_every_number_day', :number=> text_field_tag( 'recurring_todo[daily_every_x_days]', "1", {"size" => 3, "tabindex" => next_tab_index})) %><br/>
<%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_x_day', true, {:tabindex => next_tab_index})%> <%= raw t('todos.recurrence.daily_every_number_day', :number=> text_field_tag( 'recurring_todo[daily_every_x_days]', "1", {"size" => 3, "tabindex" => next_tab_index})) %><br/>
<%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_work_day', false, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.every_work_day') %><br/>
</div>
<div id="recurring_weekly" style="display:none">
<label><%= t('todos.recurrence.weekly_options') %></label><br/>
<%= t('todos.recurrence.weekly_every_number_week', :number => text_field_tag('recurring_todo[weekly_every_x_week]', 1, {"size" => 3, "tabindex" => next_tab_index})) %><br/>
<%= raw t('todos.recurrence.weekly_every_number_week', :number => text_field_tag('recurring_todo[weekly_every_x_week]', 1, {"size" => 3, "tabindex" => next_tab_index})) %><br/>
<% week_day = Time.new.wday -%>
<%# TODO: this should ideally use the 'week starts on' preferences setting, too? %>
<%= check_box_tag('recurring_todo[weekly_return_monday]', 'm', week_day == 1 ? true : false, {:tabindex => next_tab_index}) %> <%= t('date.day_names')[1] %>
@ -58,20 +58,20 @@
</div>
<div id="recurring_monthly" style="display:none">
<label><%= t('todos.recurrence.monthly_options') %></label><br/>
<%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_x_day', true, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.day_x_on_every_x_month',
<%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_x_day', true, {:tabindex => next_tab_index})%> <%= raw t('todos.recurrence.day_x_on_every_x_month',
:day => text_field_tag('recurring_todo[monthly_every_x_day]', Time.zone.now.mday, {"size" => 3, "tabindex" => next_tab_index}),
:month => text_field_tag('recurring_todo[monthly_every_x_month]', 1, {"size" => 3, "tabindex" => 10})) %><br/>
<%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_xth_day')%> <%= t('todos.recurrence.monthly_every_xth_day',
<%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_xth_day')%> <%= raw t('todos.recurrence.monthly_every_xth_day',
:day => select_tag('recurring_todo[monthly_every_xth_day]', options_for_select(@xth_day), {}),
:day_of_week => select_tag('recurring_todo[monthly_day_of_week]' , options_for_select(@days_of_week, Time.zone.now.wday), {}),
:month => text_field_tag('recurring_todo[monthly_every_x_month2]', 1, {"size" => 3, "tabindex" => 11})) %><br/>
</div>
<div id="recurring_yearly" style="display:none">
<label><%= t('todos.recurrence.yearly_options') %></label><br/>
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_x_day', true, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.yearly_every_x_day',
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_x_day', true, {:tabindex => next_tab_index})%> <%= raw t('todos.recurrence.yearly_every_x_day',
:month => select_tag('recurring_todo[yearly_month_of_year]', options_for_select(@months_of_year, Time.zone.now.month), {:tabindex => next_tab_index}),
:day => text_field_tag('recurring_todo[yearly_every_x_day]', Time.zone.now.day, "size" => 3, "tabindex" => next_tab_index)) %><br/>
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_xth_day', false, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.yearly_every_xth_day',
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_xth_day', false, {:tabindex => next_tab_index})%> <%= raw t('todos.recurrence.yearly_every_xth_day',
:day => select_tag('recurring_todo[yearly_every_xth_day]', options_for_select(@xth_day), {:tabindex => next_tab_index}),
:day_of_week => select_tag('recurring_todo[yearly_day_of_week]', options_for_select(@days_of_week, Time.zone.now.wday), {:tabindex => next_tab_index}),
:month => select_tag('recurring_todo[yearly_month_of_year2]', options_for_select(@months_of_year, Time.zone.now.month), {:tabindex => next_tab_index})) %><br/>
@ -81,7 +81,7 @@
<%= radio_button_tag('recurring_todo[recurring_target]', 'due_date', true, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.recurrence_on_due_date') %>. <%= t('todos.recurrence.show_options') %>:
<%= radio_button_tag('recurring_todo[recurring_show_always]', '1', true, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.show_option_always') %>
<%= radio_button_tag('recurring_todo[recurring_show_always]', '0', false, {:tabindex => next_tab_index})%>
<%= t('todos.recurrence.show_days_before', :days => text_field_tag( 'recurring_todo[recurring_show_days_before]', "0", {"size" => 3, "tabindex" => next_tab_index})) %>
<%= raw t('todos.recurrence.show_days_before', :days => text_field_tag( 'recurring_todo[recurring_show_days_before]', "0", {"size" => 3, "tabindex" => next_tab_index})) %>
<br/>
<%= radio_button_tag('recurring_todo[recurring_target]', 'show_from_date', false, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.from_tickler') %><br/>
<br/>

View file

@ -20,7 +20,7 @@
<%= render :layout => 'show_results_collection', :object => @found_tags, :locals => { :collection_name => "found-tags", :collection_title => t('search.tags_matching_query')} do %>
<span class="tags"><% @found_tags.each do |tag| -%>
<span class="tag"><%= link_to tag.name, {:controller => "todos", :action => "tag", :id => tag.name} -%></span>
<span class="tag"><%= link_to tag.name, tag_path(tag.name) -%></span>
<% end %>
</span><br/><br/>
<% end -%>

View file

@ -1,5 +1,5 @@
<% @swf_count ||= 0 -%>
<div class="open-flash-chart"><%= swf_tag "open-flash-chart.swf",
<div class="open-flash-chart"><%= swf_tag asset_path("open-flash-chart.swf"),
:flashvars => { 'width' => width, 'height' => height, 'data' => data},
:parameters => { 'allowScriptAccess' => 'sameDomain', 'wmode' => 'transparent'},
:div_id => "chart_#{@swf_count+=1}",

View file

@ -7,10 +7,9 @@
t('stats.no_tags_available')
else
@tags_for_cloud.each do |t| %>
<%= link_to t.name,
{:controller => "todos", :action => "tag", :id => t.name},
{:style => "font-size: " + (9 + 2*(t.count.to_i-@tags_min)/@tags_divisor).to_s + "pt",
:title => t.count.to_s+" #{t('common.actions_midsentence', :count => t.count)}"}
<%= link_to t.name, tag_path(t.name), {
:style => "font-size: " + (9 + 2*(t.count.to_i-@tags_min)/@tags_divisor).to_s + "pt",
:title => t.count.to_s+" #{t('common.actions_midsentence', :count => t.count)}"}
-%> <%
end
end-%>
@ -25,10 +24,9 @@
t('stats.no_tags_available')
else
@tags_for_cloud_90days.each do |t| %>
<%= link_to t.name,
{:controller => "todos", :action => "tag", :id => t.name},
{:style => "font-size: " + (9 + 2*(t.count.to_i-@tags_min_90days)/@tags_divisor_90days).to_s + "pt",
:title => t.count.to_s+" #{t('common.actions_midsentence', :count => t.count)}"}
<%= link_to t.name, tag_path(t.name), {
:style => "font-size: " + (9 + 2*(t.count.to_i-@tags_min_90days)/@tags_divisor_90days).to_s + "pt",
:title => t.count.to_s+" #{t('common.actions_midsentence', :count => t.count)}"}
-%> <%
end
end-%>

View file

@ -8,7 +8,7 @@
<% if collapsible %>
<a href="#" class="container_toggle" id="toggle_completed"><%= image_tag("collapse.png") %></a>
<% end %>
<%= t('todos.completed_actions') %> <%= suffix %>
<%= t('todos.completed_actions') %> <%= raw suffix %>
</h2>
<div id="completed_containeritems" class="items toggle_target">

View file

@ -3,7 +3,7 @@
<% if collapsible %>
<a href="#" class="container_toggle" id="toggle_deferred"><%= image_tag("collapse.png") %></a>
<% end %>
<%= t('todos.deferred_pending_actions') %> <%= append_descriptor ? append_descriptor : '' %>
<%= t('todos.deferred_pending_actions') %> <%= raw(append_descriptor ? append_descriptor : '') %>
</h2>
<div id="tickleritems" class="items toggle_target">

View file

@ -30,8 +30,8 @@
<input id="<%= dom_id(@todo, 'context_name') %>" name="context_name" autocomplete="off" tabindex="<%= next_tab_index%>" size="30" type="text" value="<%= h @todo.context.name %>" />
</div>
<label class="tag_list_label" for="<%= dom_id(@todo, 'todo_tag_list') %>"><%= t('todos.tags') %></label>
<%= text_field_tag 'todo_tag_list', tag_list_text, :id => dom_id(@todo, 'todo_tag_list'), :size => 30, :tabindex => next_tab_index %>
<label class="tag_list_label" for="<%= dom_id(@todo, 'tag_list') %>"><%= t('todos.tags') %></label>
<%= text_field_tag 'tag_list', tag_list_text, :id => dom_id(@todo, 'tag_list'), :size => 30, :tabindex => next_tab_index %>
<div class="due_input">
<label for="<%= dom_id(@todo, 'due_label') %>"><%= Todo.human_attribute_name('due') %></label>

View file

@ -6,8 +6,8 @@
<% this_year = current_user.time.to_date.strftime("%Y").to_i -%>
<h2><label for="todo_description"><%= t('common.description') %></label></h2>
<%= text_field( "todo", "description", "tabindex" => 1, "maxlength" => 100, "size" => 50) %>
<h2><label for="todo_tag_list"><%= t('todos.tags') %></label></h2>
<%= text_field_tag "todo_tag_list", @tag_list_text, :size => 50, :tabindex => 2 %>
<h2><label for="tag_list"><%= t('todos.tags') %></label></h2>
<%= text_field_tag "tag_list", @tag_list_text, :size => 50, :tabindex => 2 %>
<h2><label for="todo_context_id"><%= t('common.context') %></label></h2>
<%= unless @mobile_from_context
collection_select( "todo", "context_id", @contexts, "id", "name", {}, {"tabindex" => 3} )

View file

@ -3,7 +3,7 @@
<% if collapsible %>
<a href="#" class="container_toggle" id="toggle_deferred"><%= image_tag("collapse.png") %></a>
<% end %>
<%= t('todos.hidden_actions') %> <%= append_descriptor ? append_descriptor : '' %>
<%= t('todos.hidden_actions') %> <%= raw(append_descriptor ? append_descriptor : '') %>
</h2>
<div id="hiddenitems" class="items toggle_target">

View file

@ -30,9 +30,9 @@
<label for="todo_context_name"><%= Todo.human_attribute_name('context') %></label>
<input id="todo_context_name" name="context_name" autocomplete="off" tabindex="<%= next_tab_index%>" size="30" type="text" value="<%= h(@initial_context_name) %>" />
<label for="todo_tag_list"><%= Todo.human_attribute_name('tags') + ' (' + t('shared.separate_tags_with_commas') + ')' %></label>
<label for="tag_list"><%= Todo.human_attribute_name('tags') + ' (' + t('shared.separate_tags_with_commas') + ')' %></label>
<%= hidden_field_tag "initial_tag_list", @initial_tags%>
<%= text_field_tag "todo_tag_list", @initial_tags, :size => 30, :tabindex => next_tab_index %>
<%= text_field_tag "tag_list", @initial_tags, :size => 30, :tabindex => next_tab_index %>
<%= content_tag("div", "", :id => "tag_list_auto_complete", :class => "auto_complete") %>
<div class="due_input">

View file

@ -5,6 +5,7 @@ suppress_dependencies ||= false
parameters = "_source_view=#{@source_view}"
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
@z_index_counter = @z_index_counter - 1 # for IE z-index bug
@rec_depth ||= 0
%>
<div id="<%= dom_id(successor, 'successor') %>" class="item-container">
<div id="<%= dom_id(successor, 'successor_line') %>">
@ -13,16 +14,21 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
<%= remote_delete_dependency(successor, predecessor) %>
<% unless successor.pending_successors.empty? %>
<div class="todo_successors" id="<%= dom_id(successor, 'successors') %>">
<%= render :partial => "todos/successor",
:collection => successor.pending_successors,
:locals => { :todo => successor,
:parent_container_type => parent_container_type,
:suppress_dependencies => true,
:predecessor => successor }
%>
</div>
<% unless successor.pending_successors.empty?
if @rec_depth < 8
@rec_depth+=1 %>
<div class="todo_successors" id="<%= dom_id(successor, 'successors') %>">
<%= render :partial => "todos/successor",
:collection => successor.pending_successors,
:locals => { :todo => successor,
:parent_container_type => parent_container_type,
:suppress_dependencies => true,
:predecessor => successor }
%>
</div>
<% else %>
<a title="There are more dependencies that are not shown">[...]</a>
<% end %>
<% end %>
</div>
</div>

View file

@ -5,6 +5,7 @@ suppress_edit_button ||= todo.completed?
parameters = "_source_view=#{@source_view}"
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
@z_index_counter = @z_index_counter - 1 # for IE z-index bug
@rec_depth=0 # to prevent stack overflow on large dependency trees, see _successor.html.erb
%>
<div id="<%= dom_id(todo) %>" class="item-container">
<div id="<%= dom_id(todo, 'line') %>" class="item-show">

View file

@ -12,7 +12,7 @@ xml.rss :version => "2.0" do
xml.title h(todo.description)
xml.description feed_content_for_todo(todo)
xml.pubDate todo.created_at.to_s(:rfc822)
xml.link todo.project ? project_url(todo.project) : context_url(todo.context)
xml.link (todo.project && !todo.project.is_a?(NullProject)) ? project_url(todo.project) : context_url(todo.context)
xml.guid todo_url(todo)
end
end

View file

@ -1,39 +1,39 @@
<h2>Description</h2>
<h2><%= t('common.description') %></h2>
<%= @todo.description %><br/>
<h2>Actions</h2>
<h2><Actions><%= t('common.actions') %></h2>
<form method="get" action="<%= edit_todo_path(@todo, :format => :m)%>">
<button>Edit this action</button>
<button><%=t('todos.edit_action')%></button>
<input type="hidden" name="_method" value="put" />
</form>
<form method="post" action="<%=toggle_star_todo_path(@todo, :format=>:m)%>">
<button>Toggle Star</button>
<button><%=t('todos.star_action')%></button>
<input type="hidden" name="_method" value="put" />
</form>
<form method="post" action="<%=toggle_check_todo_path(@todo, :format=>:m)%>">
<button>Mark complete</button>
<button><%= t('todos.mark_complete')%></button>
<input type="hidden" name="_method" value="put" />
</form>
<form method="post" action="<%=defer_todo_path(@todo, :format=>:m, :days => 1)%>">
<button>Defer 1 day</button>
<button><%=t('todos.defer_x_days', :count => 1)%></button>
<input type="hidden" name="_method" value="put" />
</form>
<form method="post" action="<%=defer_todo_path(@todo, :format=>:m, :days => 2)%>">
<button>Defer 2 days</button>
<button><%=t('todos.defer_x_days', :count => 2)%></button>
<input type="hidden" name="_method" value="put" />
</form>
<form method="post" action="<%=defer_todo_path(@todo, :format=>:m, :days => 3)%>">
<button>Defer 3 days</button>
<button><%=t('todos.defer_x_days', :count => 3)%></button>
<input type="hidden" name="_method" value="put" />
</form>
<form method="post" action="<%=defer_todo_path(@todo, :format=>:m, :days => 7)%>">
<button>Defer 7 days</button>
<button><%=t('todos.defer_x_days', :count => 7)%></button>
<input type="hidden" name="_method" value="put" />
</form>

View file

@ -1,4 +1,4 @@
<label for="user[password]"><%= t('users.new_password_label') %>:</label><br/>
<%= password_field "user", "password", :size => 40 %><br/>
<%= password_field "user", "password", :size => 40, :autocomplete => "off" %><br/>
<label for="user[password_confirmation]"><%= t('users.password_confirmation_label') %>:</label><br/>
<%= password_field "user", "password_confirmation", :size => 40 %><br/>
<%= password_field "user", "password_confirmation", :size => 40, :autocomplete => "off" %><br/>

View file

@ -1,65 +0,0 @@
source :gemcutter
source "http://gems.github.com/"
gem "rake", "~>0.8.7"
gem "rails", "~>2.3.12"
gem "highline", "~>1.5.0"
gem "RedCloth", "4.2.8"
gem "sanitize", "~>1.2.1"
gem "rack", "1.1.0"
gem "will_paginate", "~> 2.3.15"
gem "has_many_polymorphs", "~> 2.13"
gem "acts_as_list", "~>0.1.4"
gem "aasm", "~>2.2.0"
gem "rubyjedi-actionwebservice", :require => "actionwebservice"
gem "rubycas-client", "~>2.2.1"
gem "ruby-openid", :require => "openid"
# you may comment out the database driver you will not be using.
# This will prevent a native build of the driver. Building native drivers is not always possible on all hosters
gem "sqlite3"
gem "mysql"
gem 'bcrypt-ruby', '~> 2.1.4'
gem 'htmlentities', '~> 4.3.0'
gem "mail"
if RUBY_VERSION.to_f >= 1.9
gem "soap4r-ruby1.9"
else
gem "soap4r", "~>1.5.8"
end
group :development do
if RUBY_VERSION.to_f >= 1.9
gem "ruby-debug19"
gem "mongrel", "1.2.0.pre2"
else
gem "ruby-debug"
gem "mongrel"
end
gem "yard"
end
group :test do
gem "test-unit", "1.2.3"
gem "flexmock"
gem "ZenTest", ">=4.0.0"
gem "hpricot"
gem "hoe"
gem "rspec-rails", "~>1.3.3"
gem "thoughtbot-factory_girl"
gem 'memory_test_fix', '~>0.1.3'
gem "capybara", ">=0.3.5"
gem "selenium-webdriver" # Note that > 2.14 has problems: https://code.google.com/p/selenium/issues/detail?id=3075
gem "database_cleaner", ">=0.5.0"
gem "cucumber-rails", "~>0.3.2"
gem "aruba", "0.2.2", :path => "vendor/gems/aruba-0.2.2"
# uncomment to use the webkit option. This depends on Qt to be installed
#gem "capybara-webkit"
# uncomment to be able to make screenshots from scenarios
#gem "capybara-screenshot"
#gem "launchy"
end

View file

@ -1,39 +0,0 @@
source 'https://rubygems.org'
gem 'rails', '3.2.3'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'sqlite3'
gem 'mysql'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', :platform => :ruby
gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'
# To use Jbuilder templates for JSON
# gem 'jbuilder'
# Use unicorn as the app server
# gem 'unicorn'
# Deploy with Capistrano
# gem 'capistrano'
# To use debugger
# gem 'ruby-debug19', :require => 'ruby-debug'

View file

@ -1,22 +0,0 @@
class TodoApi < ActionWebService::API::Base
api_method :new_todo,
:expects => [{:username => :string}, {:token => :string}, {:context_id => :int}, {:description => :string}, {:notes => :string}],
:returns => [:int]
api_method :new_todo_for_project,
:expects => [{:username => :string}, {:token => :string}, {:context_id => :int}, {:project_id => :int}, {:description => :string}, {:notes => :string}],
:returns => [:int]
api_method :new_rich_todo,
:expects => [{:username => :string}, {:token => :string}, {:default_context_id => :int}, {:description => :string}, {:notes => :string}],
:returns => [:int]
api_method :list_contexts,
:expects => [{:username => :string}, {:token => :string}],
:returns => [[Context]]
api_method :list_projects,
:expects => [{:username => :string}, {:token => :string}],
:returns => [[Project]]
end

View file

@ -1,301 +0,0 @@
# The methods added to this helper will be available to all templates in the
# application.
module ApplicationHelper
# Replicates the link_to method but also checks request.request_uri to find
# current page. If that matches the url, the link is marked id = "current"
#
def navigation_link(name, options = {}, html_options = nil, *parameters_for_method_reference)
if html_options
html_options = html_options.stringify_keys
convert_options_to_javascript!(html_options)
tag_options = tag_options(html_options)
else
tag_options = nil
end
url = options.is_a?(String) ? options : self.url_for(options, *parameters_for_method_reference)
id_tag = (request.request_uri == url) ? " id=\"current\"" : ""
"<a href=\"#{url}\"#{tag_options}#{id_tag}>#{name || url}</a>"
end
def days_from_today(date)
date.in_time_zone.to_date - current_user.time.to_date
end
# Check due date in comparison to today's date Flag up date appropriately with
# a 'traffic light' colour code
#
def due_date(due)
return "" if due.nil?
days = days_from_today(due)
colors = ['amber','amber','orange','orange','orange','orange','orange','orange']
color = :red if days < 0
color = :green if days > 7
color = colors[days] if color.nil?
return content_tag(:a, {:title => format_date(due)}) {
content_tag(:span, {:class => color}) {
case days
when 0
t('todos.next_actions_due_date.due_today')
when 1
t('todos.next_actions_due_date.due_tomorrow')
when 2..7
if prefs.due_style == Preference.due_styles[:due_on]
# TODO: internationalize strftime here
t('models.preference.due_on', :date => due.strftime("%A"))
else
t('models.preference.due_in', :days => days)
end
else
# overdue or due very soon! sound the alarm!
if days == -1
t('todos.next_actions_due_date.overdue_by', :days => days * -1)
elsif days < -1
t('todos.next_actions_due_date.overdue_by_plural', :days => days * -1)
else
# more than a week away - relax
t('models.preference.due_in', :days => days)
end
end
}
}
end
# Check due date in comparison to today's date Flag up date appropriately with
# a 'traffic light' colour code Modified method for mobile screen
#
def due_date_mobile(due)
if due == nil
return ""
end
days = days_from_today(due)
case days
when 0
"<span class=\"amber\">"+ format_date(due) + "</span>"
when 1
"<span class=\"amber\">" + format_date(due) + "</span>"
# due 2-7 days away
when 2..7
"<span class=\"orange\">" + format_date(due) + "</span>"
else
# overdue or due very soon! sound the alarm!
if days < 0
"<span class=\"red\">" + format_date(due) +"</span>"
else
# more than a week away - relax
"<span class=\"green\">" + format_date(due) + "</span>"
end
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")
@controller.count_undone_todos_phrase(todos_parent, string)
end
def count_undone_todos_phrase_text(todos_parent, string="actions")
count_undone_todos_phrase(todos_parent, string).gsub("&nbsp;"," ")
end
def count_undone_todos_and_notes_phrase(project, string="actions")
s = count_undone_todos_phrase(project, string)
s += ", #{pluralize(project.note_count, 'note')}" unless project.note_count == 0
s
end
def link_to_context(context, descriptor = sanitize(context.name))
link_to( descriptor, context, :title => "View context: #{context.name}" )
end
def link_to_project(project, descriptor = sanitize(project.name))
link_to( descriptor, project, :title => "View project: #{project.name}" )
end
def link_to_edit_note (note, descriptor = sanitize(note.id.to_s))
link_to(descriptor,
url_for({:controller => 'notes', :action => 'edit', :id => note.id}),
{:id => "link_edit_#{dom_id(note)}", :class => "note_edit_settings"})
end
def link_to_project_mobile(project, accesskey, descriptor = sanitize(project.name))
link_to( descriptor, project_path(project, :format => 'm'), {:title => "View project: #{project.name}", :accesskey => accesskey} )
end
def item_link_to_context(item)
descriptor = "[C]"
descriptor = "[#{item.context.name}]" if prefs.verbose_action_descriptors
link_to_context( item.context, descriptor )
end
def item_link_to_project(item)
descriptor = "[P]"
descriptor = "[#{item.project.name}]" if prefs.verbose_action_descriptors
link_to_project( item.project, descriptor )
end
def render_flash
render :partial => 'shared/flash', :object => flash
end
def recurrence_time_span(rt)
case rt.ends_on
when "no_end_date"
return rt.start_from.nil? ? "" : I18n.t("todos.recurrence.pattern.from") + " " + format_date(rt.start_from)
when "ends_on_number_of_times"
return I18n.t("todos.recurrence.pattern.times", :number => rt.number_of_occurences)
when "ends_on_end_date"
starts = rt.start_from.nil? ? "" : I18n.t("todos.recurrence.pattern.from") + " " + format_date(rt.start_from)
ends = rt.end_date.nil? ? "" : " " + I18n.t("todos.recurrence.pattern.until") + " " + format_date(rt.end_date)
return starts+ends
else
raise Exception.new, "unknown recurrence time span selection (#{rt.ends_on})"
end
end
def recurrence_pattern_as_text(recurring_todo)
rt = recurring_todo.recurring_target_as_text
rp = recurring_todo.recurrence_pattern
# only add space if recurrence_pattern has content
rp = " " + rp if !rp.nil?
rts = recurrence_time_span(recurring_todo)
# only add space if recurrence_time_span has content
rts = " " + rts if !(rts == "")
return rt+rp+rts
end
def date_format_for_date_picker()
standard_format = current_user.prefs.date_format
translations = [
['%m', 'mm'],
['%b', 'M'],
['%B', 'MM'],
['%d', 'dd'],
['%a', 'D'],
['%A', 'DD'],
['%y', 'y'],
['%Y', 'yy']
]
translations.inject(standard_format) do |str, translation|
str.gsub(*translation)
end
end
AUTO_LINK_MESSAGE_RE = %r{message://<[^>]+>} unless const_defined?(:AUTO_LINK_MESSAGE_RE)
# Converts message:// links to href. This URL scheme is used on Mac OS X
# to link to a mail message in Mail.app.
def auto_link_message(text)
text.gsub(AUTO_LINK_MESSAGE_RE) do
href = $&
left, right = $`, $'
# detect already linked URLs and URLs in the middle of a tag
if left =~ /<[^>]+$/ && right =~ /^[^>]*>/
# do not change string; URL is alreay linked
href
else
content = content_tag(:a, h(href), :href => h(href))
end
end
end
def format_note(note)
note = auto_link_message(note)
note = markdown(note)
note = auto_link(note, :link => :urls)
# add onenote and message protocols
Sanitize::Config::RELAXED[:protocols]['a']['href'] << 'onenote'
Sanitize::Config::RELAXED[:protocols]['a']['href'] << 'message'
note = Sanitize.clean(note, Sanitize::Config::RELAXED)
return note
end
def sidebar_html_for_titled_list (list, title)
return content_tag(:h3, title+" (#{list.length})") +
content_tag(:ul, sidebar_html_for_list(list))
end
def sidebar_html_for_list(list)
if list.empty?
return content_tag(:li, t('sidebar.list_empty'))
else
return list.inject("") do |html, item|
link = (item.class == "Project") ? link_to_project( item ) : link_to_context(item)
html << content_tag(:li, link + " (" + count_undone_todos_phrase(item,"actions")+")")
end
end
end
def generate_i18n_strings
js = "i18n_locale='#{I18n.locale}';\n"
js << "i18n = new Array();\n"
%w{
shared.toggle_multi shared.toggle_multi_title
shared.hide_form shared.hide_action_form_title
shared.toggle_single shared.toggle_single_title
projects.hide_form projects.hide_form_title
projects.show_form projects.show_form_title
contexts.hide_form contexts.hide_form_title
contexts.show_form contexts.show_form_title
contexts.new_context_pre contexts.new_context_post
common.cancel common.ok
common.ajaxError todos.unresolved_dependency
}.each do |s|
js << "i18n['#{s}'] = '#{ t(s).gsub(/'/, "\\\\'") }';\n"
end
return js
end
def javascript_tag_for_i18n_datepicker
locale = I18n.locale
# do not include en as locale since this the available by default
if locale and locale != :en
javascript_include_tag("i18n/jquery.ui.datepicker-#{locale}.js")
end
end
def determine_done_path
case @controller.controller_name
when "contexts"
done_todos_context_path(@context)
when "projects"
done_todos_project_path(@project)
when "todos"
if source_view_is(:tag)
done_tag_path(@tag_name)
else
done_todos_path
end
else
done_todos_path
end
end
def determine_all_done_path
case @controller.controller_name
when "contexts"
all_done_todos_context_path(@context)
when "projects"
all_done_todos_project_path(@project)
when "todos"
if source_view_is(:tag)
all_done_tag_path(@tag_name)
else
all_done_todos_path
end
else
all_done_todos_path
end
end
end

View file

@ -1,73 +0,0 @@
class CannotAccessContext < RuntimeError; end
class BackendController < ApplicationController
acts_as_web_service
wsdl_service_name 'Backend'
web_service_api TodoApi
web_service_scaffold :invoke
skip_before_filter :login_required
def new_todo(username, token, context_id, description, notes)
check_token(username, token)
check_context_belongs_to_user(context_id)
item = create_todo(description, context_id, nil, notes)
item.id
end
def new_todo_for_project(username, token, context_id, project_id, description, notes)
check_token(username, token)
check_context_belongs_to_user(context_id)
item = create_todo(description, context_id, project_id, notes)
item.id
end
def new_rich_todo(username, token, default_context_id, description, notes)
check_token(username,token)
item = Todo.from_rich_message(@user, default_context_id, description, notes)
item.save
raise item.errors.full_messages.to_s if item.new_record?
item.id
end
def list_contexts(username, token)
check_token(username, token)
@user.contexts
end
def list_projects(username, token)
check_token(username, token)
@user.projects
end
private
# Check whether the token in the URL matches the token in the User's table
def check_token(username, token)
@user = User.find_by_login( username )
unless (token == @user.token)
raise(InvalidToken, "Sorry, you don't have permission to perform this action.")
end
end
def check_context_belongs_to_user(context_id)
unless @user.contexts.exists? context_id
raise(CannotAccessContext, "Cannot access a context that does not belong to this user.")
end
end
def create_todo(description, context_id, project_id = nil, notes="")
item = @user.todos.build
item.description = description
item.notes = notes
item.context_id = context_id
item.project_id = project_id unless project_id.nil?
item.save
raise item.errors.full_messages.to_s if item.new_record?
item
end
end
class InvalidToken < RuntimeError; end

View file

@ -1,37 +0,0 @@
# MySQL. Versions 4.1 and 5.0 are recommended.
#
#
# Be sure to use new-style password hashing:
# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
development:
adapter: mysql
database: tracks_trunk
encoding: utf8
host: localhost
username: tracks
password: 32tracks55
mdevelopment:
adapter: sqlite3
database: db/tracks-21-test.sqlite3.db
test: &TEST
# adapter: sqlite3
# database: ":memory:"
# verbosity: quiet
adapter: mysql
database: tracks_test
host: localhost
username: tracks
password: 32tracks55
production:
adapter: mysql
database: tracks_trunk
encoding: utf8
host: localhost
username: tracks
password: 32tracks55
cucumber:
<<: *TEST

View file

@ -1,19 +0,0 @@
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
# Unique cookies
config.action_controller.session = { :key => 'TracksDev' }
NOTIFY_BAR="<div id=\"develop-notify-bar\">&nbsp;</div>"

View file

@ -1,70 +0,0 @@
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
# It is recommended to regenerate this file in the future when you upgrade to a
# newer version of cucumber-rails. Consider adding your own code to a new file
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
# files.
ENV["RAILS_ENV"] ||= "cucumber"
require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
# require 'cucumber/rails/rspec'
require 'cucumber/rails/world'
require 'cucumber/rails/active_record'
require 'cucumber/web/tableish'
require 'aruba/cucumber'
require 'capybara/rails'
require 'capybara/cucumber'
require 'capybara/session'
# BUG in this version of cucumber/capybara: require 'cucumber/rails/capybara_javascript_emulation' # Lets you click links with onclick javascript handlers without using @culerity or @javascript
Capybara.default_wait_time = 5
Capybara.javascript_driver = ENV["JS_DRIVER"] ? ENV["JS_DRIVER"].to_sym : :selenium
if Capybara.javascript_driver == :webkit
require 'capybara/webkit'
end
# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In
# order to ease the transition to Capybara we set the default here. If you'd
# prefer to use XPath just remove this line and adjust any selectors in your
# steps to use the XPath syntax.
Capybara.default_selector = :css
Capybara.prefer_visible_elements = true
# If you set this to false, any error raised from within your app will bubble
# up to your step definition and out to cucumber unless you catch it somewhere
# on the way. You can make Rails rescue errors and render error pages on a
# per-scenario basis by tagging a scenario or feature with the @allow-rescue tag.
#
# If you set this to true, Rails will rescue all errors and render error
# pages, more or less in the same way your application would behave in the
# default production environment. It's not recommended to do this for all
# of your scenarios, as this makes it hard to discover errors in your application.
ActionController::Base.allow_rescue = false
# If you set this to true, each scenario will run in a database transaction.
# You can still turn off transactions on a per-scenario basis, simply tagging
# a feature or scenario with the @no-txn tag. If you are using Capybara,
# tagging with @culerity or @javascript will also turn transactions off.
#
# If you set this to false, transactions will be off for all scenarios,
# regardless of whether you use @no-txn or not.
#
# Beware that turning transactions off will leave data in your database
# after each scenario, which can lead to hard-to-debug failures in
# subsequent scenarios. If you do this, we recommend you create a Before
# block that will explicitly put your database in a known state.
Cucumber::Rails::World.use_transactional_fixtures = true
# How to clean your database when transactions are turned off. See
# http://github.com/bmabey/database_cleaner for more info.
if defined?(ActiveRecord::Base)
begin
require 'database_cleaner'
DatabaseCleaner.strategy = :truncation
rescue LoadError => ignore_if_database_cleaner_not_present
end
end

View file

@ -1,119 +0,0 @@
# Be sure to restart your webserver when you modify this file.
# Uncomment below to force Rails into production mode
# (Use only when you can't set environment variables through your web/app server)
# ENV['RAILS_ENV'] = 'production'
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
require 'yaml'
SITE_CONFIG = YAML.load_file(File.join(File.dirname(__FILE__), 'site.yml'))
class Rails::Configuration
attr_accessor :action_web_service
end
Encoding.default_external = Encoding::UTF_8 if RUBY_VERSION > "1.9"
Rails::Initializer.run do |config|
# Skip frameworks you're not going to use
# config.frameworks -= [ :action_web_service, :action_mailer ]
config.autoload_paths += %W( #{RAILS_ROOT}/app/apis )
config.action_controller.use_accept_header = true
# Use the database for sessions instead of the file system
# (create the session table with 'rake create_sessions_table')
config.action_controller.session_store = :active_record_store
config.action_controller.session = {
:key => '_tracks_session_id',
:secret => SITE_CONFIG['salt'] * (30.0 / SITE_CONFIG['salt'].length).ceil #must be at least 30 characters
}
config.action_controller.relative_url_root = SITE_CONFIG['subdir'] if SITE_CONFIG['subdir']
# Enable page/fragment caching by setting a file-based store
# (remember to create the caching directory and make it readable to the application)
# config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
# Activate observers that should always be running
# config.active_record.observers = :cacher, :garbage_collector
# Make Active Record use UTC-base instead of local time
config.active_record.default_timezone = :utc
# You''ll probably want to change this to the time zone of the computer where Tracks is running
# run rake time:zones:local have Rails suggest time zone names on your system
config.time_zone = SITE_CONFIG['time_zone']
# Use Active Record's schema dumper instead of SQL when creating the test database
# (enables use of different database adapters for development and test environments)
config.active_record.schema_format = :ruby
# allow other protocols in urls for sanitzer. Add to your liking, for example
# config.action_view.sanitized_allowed_protocols = 'onenote', 'blah', 'proto'
# to enable "link":onenote://... or "link":blah://... hyperlinks
config.action_view.sanitized_allowed_protocols = 'onenote', 'message'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
end
# Add new inflection rules using the following format
# (all these examples are active by default):
# Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
# Include your application configuration below
require 'name_part_finder'
require 'tracks/todo_list'
require 'tracks/config'
require 'tagging_extensions' # Needed for tagging-specific extensions
require 'digest/sha1' #Needed to support 'rake db:fixtures:load' on some ruby installs: http://dev.rousette.org.uk/ticket/557
if ( SITE_CONFIG['authentication_schemes'].include? 'ldap')
require 'net/ldap' #requires ruby-net-ldap gem be installed
require 'simple_ldap_authenticator'
ldap = SITE_CONFIG['ldap']
SimpleLdapAuthenticator.ldap_library = ldap['library']
SimpleLdapAuthenticator.servers = ldap['servers']
SimpleLdapAuthenticator.use_ssl = ldap['ssl']
SimpleLdapAuthenticator.login_format = ldap['login_format']
end
if ( SITE_CONFIG['authentication_schemes'].include? 'open_id')
#requires ruby-openid gem to be installed
OpenID::Util.logger = RAILS_DEFAULT_LOGGER
end
if ( SITE_CONFIG['authentication_schemes'].include? 'cas')
#requires rubycas-client gem to be installed
if defined? CASClient
require 'casclient/frameworks/rails/filter'
CASClient::Frameworks::Rails::Filter.configure(
:cas_base_url => SITE_CONFIG['cas_server'] ,
:cas_server_logout => SITE_CONFIG['cas_server_logout']
)
end
end
# changed in development.rb to show under_construction bar
NOTIFY_BAR = "" unless defined?(NOTIFY_BAR)
tracks_version='2.2devel'
# comment out next two lines if you do not want (or can not) the date of the
# last git commit in the footer
info=`git log --pretty=format:"%ai" -1`
tracks_version=tracks_version + ' ('+info+')'
TRACKS_VERSION=tracks_version

View file

@ -1,84 +0,0 @@
(function($){
/* hoverIntent by Brian Cherne */
$.fn.hoverIntent = function(f,g) {
// default configuration options
var cfg = {
sensitivity: 7,
interval: 100,
timeout: 400
};
// override configuration options with user supplied object
cfg = $.extend(cfg, g ? { over: f, out: g } : f );
// instantiate variables
// cX, cY = current X and Y position of mouse, updated by mousemove event
// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
var cX, cY, pX, pY;
// A private function for getting mouse position
var track = function(ev) {
cX = ev.pageX;
cY = ev.pageY;
};
// A private function for comparing current and previous mouse position
var compare = function(ev,ob) {
ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
// compare mouse positions to see if they've crossed the threshold
if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
$(ob).unbind("mousemove",track);
// set hoverIntent state to true (so mouseOut can be called)
ob.hoverIntent_s = 1;
return cfg.over.apply(ob,[ev]);
} else {
// set previous coordinates for next time
pX = cX; pY = cY;
// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
}
};
// A private function for delaying the mouseOut function
var delay = function(ev,ob) {
ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
ob.hoverIntent_s = 0;
return cfg.out.apply(ob,[ev]);
};
// A private function for handling mouse 'hovering'
var handleHover = function(e) {
// next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
if ( p == this ) { return false; }
// copy objects to be passed into t (required for event object to be passed in IE)
var ev = jQuery.extend({},e);
var ob = this;
// cancel hoverIntent timer if it exists
if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
// else e.type == "onmouseover"
if (e.type == "mouseover") {
// set "previous" X and Y position based on initial entry point
pX = ev.pageX; pY = ev.pageY;
// update "current" X and Y position based on mousemove
$(ob).bind("mousemove",track);
// start polling interval (self-calling timeout) to compare mouse coordinates over time
if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
// else e.type == "onmouseout"
} else {
// unbind expensive mousemove event
$(ob).unbind("mousemove",track);
// if hoverIntent state is true, then call the mouseOut function after the specified delay
if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
}
};
// bind the function to the two event listeners
return this.mouseover(handleHover).mouseout(handleHover);
};
})(jQuery);

View file

@ -1,113 +0,0 @@
module AuthenticatedTestHelper
# Sets the current user in the session from the user fixtures.
def login_as(user)
@request.session['user_id'] = user ? users(user).id : nil
end
def content_type(type)
@request.env['Content-Type'] = type
end
def accept(accept)
@request.env["HTTP_ACCEPT"] = accept
end
def authorize_as(user)
if user
@request.env["HTTP_AUTHORIZATION"] = "Basic #{Base64.encode64("#{users(user).login}:test")}"
accept 'application/xml'
content_type 'application/xml'
else
@request.env["HTTP_AUTHORIZATION"] = nil
accept nil
content_type nil
end
end
# http://project.ioni.st/post/217#post-217
#
# def test_new_publication
# assert_difference(Publication, :count) do
# post :create, :publication => {...}
# # ...
# end
# end
#
def assert_difference(object, method = nil, difference = 1)
initial_value = object.send(method)
yield
assert_equal initial_value + difference, object.send(method), "#{object}##{method}"
end
def assert_no_difference(object, method, &block)
assert_difference object, method, 0, &block
end
# Assert the block redirects to the login
#
# assert_requires_login(:bob) { |c| c.get :edit, :id => 1 }
#
def assert_requires_login(login = nil)
yield HttpLoginProxy.new(self, login)
end
def assert_http_authentication_required(login = nil)
yield XmlLoginProxy.new(self, login)
end
def reset!(*instance_vars)
instance_vars = [:controller, :request, :response] unless instance_vars.any?
instance_vars.collect! { |v| "@#{v}".to_sym }
instance_vars.each do |var|
instance_variable_set(var, instance_variable_get(var).class.new)
end
end
end
class BaseLoginProxy
attr_reader :controller
attr_reader :options
def initialize(controller, login)
@controller = controller
@login = login
end
private
def authenticated
raise NotImplementedError
end
def check
raise NotImplementedError
end
def method_missing(method, *args)
@controller.reset!
authenticate
@controller.send(method, *args)
check
end
end
class HttpLoginProxy < BaseLoginProxy
protected
def authenticate
@controller.login_as @login if @login
end
def check
@controller.assert_redirected_to :controller => 'account', :action => 'login'
end
end
class XmlLoginProxy < BaseLoginProxy
protected
def authenticate
@controller.accept 'application/xml'
@controller.authorize_as @login if @login
end
def check
@controller.assert_response 401
end
end

View file

@ -1,222 +0,0 @@
require_dependency "user"
module LoginSystem
def current_user
get_current_user
end
def prefs
current_user.prefs unless current_user.nil?
end
# Logout the {#current_user} and redirect to login page
#
# @param [String] message notification to display
def logout_user message=t('login.logged_out')
@user.forget_me if logged_in?
cookies.delete :auth_token
session['user_id'] = nil
if ( SITE_CONFIG['authentication_schemes'].include? 'cas') && session[:cas_user]
CASClient::Frameworks::Rails::Filter.logout(self)
else
reset_session
notify :notice, message
redirect_to_login
end
end
protected
# overwrite this if you want to restrict access to only a few actions
# or if you want to check if the user has the correct rights
# example:
#
# # only allow nonbobs
# def authorize?(user)
# user.login != "bob"
# end
def authorize?(user)
true
end
# overwrite this method if you only want to protect certain actions of the controller
# example:
#
# # don't protect the login and the about method
# def protect?(action)
# if ['action', 'about'].include?(action)
# return false
# else
# return true
# end
# end
def protect?(action)
true
end
# When called with before_filter :login_from_cookie will check for an :auth_token
# cookie and log the user back in if appropriate
def login_from_cookie
return unless cookies[:auth_token] && !logged_in?
token = cookies[:auth_token]
user = User.where(:remember_token => token)
if user && user.remember_token?
session['user_id'] = user.id
set_current_user(user)
current_user.remember_me
cookies[:auth_token] = { :value => current_user.remember_token , :expires => current_user.remember_token_expires_at, :secure => SITE_CONFIG['secure_cookies'] }
flash[:notice] = t('login.successful')
end
end
def login_or_feed_token_required
if ['rss', 'atom', 'txt', 'ics'].include?(params[:format])
if user = User.find_by_token(params[:token])
set_current_user(user)
return true
end
end
login_required
end
# login_required filter. add
#
# before_filter :login_required
#
# if the controller should be under any rights management.
# for finer access control you can overwrite
#
# def authorize?(user)
#
def login_required
if not protect?(action_name)
return true
end
login_from_cookie
if session['user_id'] and authorize?(get_current_user)
return true
end
http_user, http_pass = get_basic_auth_data
if user = User.authenticate(http_user, http_pass)
session['user_id'] = user.id
set_current_user(user)
return true
end
# store current location so that we can
# come back after the user logged in
store_location unless params[:format] == 'js'
# call overwriteable reaction to unauthorized access
access_denied
return false
end
def login_optional
login_from_cookie
if session['user_id'] and authorize?(get_current_user)
return true
end
http_user, http_pass = get_basic_auth_data
if user = User.authenticate(http_user, http_pass)
session['user_id'] = user.id
set_current_user(user)
return true
end
return true
end
def logged_in?
current_user != nil
end
def get_current_user
if @user.nil? && session['user_id']
@user = User.find(session['user_id'])
end
@user
end
def set_current_user(user)
@user = user
end
# overwrite if you want to have special behavior in case the user is not authorized
# to access the current operation.
# the default action is to redirect to the login screen
# example use :
# a popup window might just close itself for instance
def access_denied
respond_to do |format|
format.html { redirect_to login_path }
format.m { redirect_to formatted_login_path(:format => 'm') }
format.js { render :partial => 'login/redirect_to_login' }
format.xml { basic_auth_denied }
format.rss { basic_auth_denied }
format.atom { basic_auth_denied }
format.text { basic_auth_denied }
end
end
# store current uri in the session.
# we can return to this location by calling return_location
def store_location
session['return-to'] = request.request_uri
end
# move to the last store_location call or to the passed default one
def redirect_back_or_default(default)
if session['return-to'].nil?
redirect_to default
else
redirect_to session['return-to']
session['return-to'] = nil
end
end
# HTTP Basic auth code adapted from Coda Hale's simple_http_auth plugin. Thanks, Coda!
def get_basic_auth_data
auth_locations = ['REDIRECT_REDIRECT_X_HTTP_AUTHORIZATION',
'REDIRECT_X_HTTP_AUTHORIZATION',
'X-HTTP_AUTHORIZATION', 'HTTP_AUTHORIZATION']
authdata = nil
for location in auth_locations
if request.env.has_key?(location)
authdata = request.env[location].to_s.split
end
end
if authdata and authdata[0] == 'Basic'
user, pass = Base64.decode64(authdata[1]).split(':')[0..1]
else
user, pass = ['', '']
end
return user, pass
end
def basic_auth_denied
response.headers["WWW-Authenticate"] = "Basic realm=\"'Tracks Login Required'\""
render :text => t('login.unsuccessful'), :status => 401
end
private
# Redirect the user to the login page.
def redirect_to_login
respond_to do |format|
format.html { redirect_to login_path }
format.m { redirect_to login_path(:format => 'm') }
end
end
end

View file

@ -1,5 +0,0 @@
module NamePartFinder
def find_by_namepart(namepart)
find_by_name(namepart) || find(:first, :conditions => ["name LIKE ?", namepart + '%'])
end
end

View file

@ -1,200 +0,0 @@
class ActiveRecord::Base #:nodoc:
# These extensions make models taggable. This file is automatically generated and required by your app if you run the tagging generator included with has_many_polymorphs.
module TaggingExtensions
# Add tags to <tt>self</tt>. 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
taggable?(true)
tag_cast_to_string(incoming).each do |tag_name|
# added following check to prevent empty tags from being saved (which will fail)
unless tag_name.blank?
begin
tag = Tag.find_or_create_by_name(tag_name)
raise Tag::Error, "tag could not be saved: #{tag_name}" if tag.new_record?
tags << tag
rescue ActiveRecord::StatementInvalid => e
raise unless e.to_s =~ /duplicate/i
end
end
end
end
# Removes tags from <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
def _remove_tags outgoing
taggable?(true)
outgoing = tag_cast_to_string(outgoing)
tags.delete(*(tags.select do |tag|
outgoing.include? tag.name
end))
end
# Returns the tags on <tt>self</tt> as a string.
def tag_list
# Redefined later to avoid an RDoc parse error.
end
# Replace the existing tags on <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
def tag_with list
#:stopdoc:
taggable?(true)
list = tag_cast_to_string(list)
# Transactions may not be ideal for you here; be aware.
Tag.transaction do
current = tags.map(&:name)
_add_tags(list - current)
_remove_tags(current - list)
end
self
#:startdoc:
end
# Returns the tags on <tt>self</tt> as a string.
def tag_list #:nodoc:
#:stopdoc:
taggable?(true)
tags.reload
tags.to_s
#:startdoc:
end
def tag_list=(value)
tag_with(value)
end
private
def tag_cast_to_string obj #:nodoc:
case obj
when Array
obj.map! do |item|
case item
# removed next line as it prevents using numbers as tags
# when /^\d+$/, Fixnum then Tag.find(item).name # This will be slow if you use ids a lot.
when Tag then item.name
when String then item
else
raise "Invalid type"
end
end
when String
obj = obj.split(Tag::DELIMITER).map do |tag_name|
tag_name.strip.squeeze(" ")
end
else
raise "Invalid object of class #{obj.class} as tagging method parameter"
end.flatten.compact.map(&:downcase).uniq
end
# Check if a model is in the :taggables target list. The alternative to this check is to explicitly include a TaggingMethods module (which you would create) in each target model.
def taggable?(should_raise = false) #:nodoc:
unless flag = respond_to?(:tags)
raise "#{self.class} is not a taggable model" if should_raise
end
flag
end
end
module TaggingFinders
# Find all the objects tagged with the supplied list of tags
#
# Usage : Model.tagged_with("ruby")
# Model.tagged_with("hello", "world")
# Model.tagged_with("hello", "world", :limit => 10)
#
# XXX This query strategy is not performant, and needs to be rewritten as an inverted join or a series of unions
#
def tagged_with(*tag_list)
options = tag_list.last.is_a?(Hash) ? tag_list.pop : {}
tag_list = parse_tags(tag_list)
scope = scope(:find)
options[:select] ||= "#{table_name}.*"
options[:from] ||= "#{table_name}, tags, taggings"
sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
sql << "FROM #{(scope && scope[:from]) || options[:from]} "
add_joins!(sql, options[:joins], scope)
sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "
sql << "AND taggings.tag_id = tags.id "
tag_list_condition = tag_list.map {|name| "'#{name}'"}.join(", ")
sql << "AND (tags.name IN (#{sanitize_sql(tag_list_condition)})) "
sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions]
columns = column_names.map do |column|
"#{table_name}.#{column}"
end.join(", ")
sql << "GROUP BY #{columns} "
sql << "HAVING COUNT(taggings.tag_id) = #{tag_list.size}"
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope)
add_lock!(sql, options, scope)
find_by_sql(sql)
end
def self.tagged_with_any(*tag_list)
options = tag_list.last.is_a?(Hash) ? tag_list.pop : {}
tag_list = parse_tags(tag_list)
scope = scope(:find)
options[:select] ||= "#{table_name}.*"
options[:from] ||= "#{table_name}, meta_tags, taggings"
sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
sql << "FROM #{(scope && scope[:from]) || options[:from]} "
add_joins!(sql, options, scope)
sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "
sql << "AND taggings.meta_tag_id = meta_tags.id "
sql << "AND ("
or_options = []
tag_list.each do |name|
or_options << "meta_tags.name = '#{name}'"
end
or_options_joined = or_options.join(" OR ")
sql << "#{or_options_joined}) "
sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions]
columns = column_names.map do |column|
"#{table_name}.#{column}"
end.join(", ")
sql << "GROUP BY #{columns} "
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope)
add_lock!(sql, options, scope)
find_by_sql(sql)
end
def parse_tags(tags)
return [] if tags.blank?
tags = Array(tags).first
tags = tags.respond_to?(:flatten) ? tags.flatten : tags.split(Tag::DELIMITER)
tags.map { |tag| tag.strip.squeeze(" ") }.flatten.compact.map(&:downcase).uniq
end
end
include TaggingExtensions
extend TaggingFinders
end

View file

@ -1,38 +0,0 @@
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
# It is recommended to regenerate this file in the future when you upgrade to a
# newer version of cucumber-rails. Consider adding your own code to a new file
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
# files.
vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil?
begin
require 'cucumber/rake/task'
namespace :cucumber do
Cucumber::Rake::Task.new({:selenium => :env_to_selenium}, 'Run features that require selenium') do |t|
t.binary = vendored_cucumber_bin
t.fork = true # You may get faster startup if you set this to false
t.profile = 'selenium'
end
Cucumber::Rake::Task.new({:selenium_wip => :env_to_selenium}, 'Run unfinished features that require selenium') do |t|
t.binary = vendored_cucumber_bin
t.fork = true # You may get faster startup if you set this to false
t.profile = 'selenium_wip'
end
task :env_to_selenium => 'db:test:prepare' do
ENV['RAILS_ENV'] = 'selenium'
end
desc 'Run all features'
task :all => [:ok, :wip, :selenium, :selenium_wip]
end
rescue LoadError
desc 'cucumber rake task not available (cucumber not installed)'
task :cucumber do
abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
end
end

View file

@ -1,53 +0,0 @@
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
# It is recommended to regenerate this file in the future when you upgrade to a
# newer version of cucumber-rails. Consider adding your own code to a new file
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
# files.
unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks
vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil?
begin
require 'cucumber/rake/task'
namespace :cucumber do
Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
t.fork = true # You may get faster startup if you set this to false
t.profile = 'default'
end
Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
t.binary = vendored_cucumber_bin
t.fork = true # You may get faster startup if you set this to false
t.profile = 'wip'
end
Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
t.binary = vendored_cucumber_bin
t.fork = true # You may get faster startup if you set this to false
t.profile = 'rerun'
end
desc 'Run all features'
task :all => [:ok, :wip]
end
desc 'Alias for cucumber:ok'
task :cucumber => 'cucumber:ok'
task :default => :cucumber
task :features => :cucumber do
STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
end
rescue LoadError
desc 'cucumber rake task not available (cucumber not installed)'
task :cucumber do
abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
end
end
end

View file

@ -1,27 +0,0 @@
require 'rake'
namespace :db do
desc "Dump the current SQLite3 or MySQL database to a sql file"
task :dump_sql do
load 'config/environment.rb'
abcs = ActiveRecord::Base.configurations
case abcs[RAILS_ENV]["adapter"]
when 'mysql'
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
File.open("db/#{RAILS_ENV}_data.sql", "w+") do |f|
if abcs[RAILS_ENV]["password"].blank?
f << `mysqldump -h #{abcs[RAILS_ENV]["host"]} -u #{abcs[RAILS_ENV]["username"]} #{abcs[RAILS_ENV]["database"]}`
else
f << `mysqldump -h #{abcs[RAILS_ENV]["host"]} -u #{abcs[RAILS_ENV]["username"]} -p#{abcs[RAILS_ENV]["password"]} #{abcs[RAILS_ENV]["database"]}`
end
end
when 'sqlite3'
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
File.open("db/#{RAILS_ENV}_data.sql", "w+") do |f|
f << `sqlite3 #{abcs[RAILS_ENV]["database"]} .dump`
end
else
raise "Task not supported by '#{abcs[RAILS_ENV]['adapter']}'"
end
end
end

View file

@ -1,17 +0,0 @@
desc ' Create YAML test fixtures from data in an existing database.
Defaults to development database. Set RAILS_ENV to override (taken from Rails Recipes book).'
task :extract_fixtures => :environment do
sql = "SELECT * FROM %s"
skip_tables = ["schema_info", "sessions", "users"]
ActiveRecord::Base.establish_connection
(ActiveRecord::Base.connection.tables - skip_tables).each do |table_name|
i = "000"
File.open("#{RAILS_ROOT}/db/exported_fixtures/#{table_name}.yml", 'w' ) do |file|
data = ActiveRecord::Base.connection.select_all(sql % table_name)
file.write data.inject({}) { |hash, record|
hash["#{table_name}_#{i.succ!}"] = record
hash
}.to_yaml
end
end
end

View file

@ -1,34 +0,0 @@
desc "Copy third-party gems into ./lib"
task :freeze_other_gems do
# TODO Get this list from parsing environment.rb
libraries = %w(redcloth)
require 'rubygems'
require 'find'
libraries.each do |library|
library_gem = Gem.cache.search(library).sort_by { |g| g.version }.last
puts "Freezing #{library} for #{library_gem.version}..."
# TODO Add dependencies to list of libraries to freeze
#library_gem.dependencies.each { |g| libraries << g }
folder_for_library = "#{library_gem.name}-#{library_gem.version}"
system "cd vendor; gem unpack -v '#{library_gem.version}' #{library_gem.name};"
# Copy files recursively to ./lib
folder_for_library_with_lib = "vendor/#{folder_for_library}/lib/"
Find.find(folder_for_library_with_lib) do |original_file|
destination_file = "./lib/" + original_file.gsub(folder_for_library_with_lib, '')
if File.directory?(original_file)
if !File.exist?(destination_file)
Dir.mkdir destination_file
end
else
File.copy original_file, destination_file
end
end
system "rm -r vendor/#{folder_for_library}"
end
end

View file

@ -1,8 +0,0 @@
desc "Load exported fixtures (in db/exported_fixtures) into the current environment's database"
task :load_exported_fixtures => :environment do
require 'active_record/fixtures'
ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
Dir.glob(File.join(RAILS_ROOT, 'db', 'exported_fixtures', '*.{yml,csv}')).each do |fixture_file|
Fixtures.create_fixtures('db/exported_fixtures', File.basename(fixture_file, '.*'))
end
end

View file

@ -1,50 +0,0 @@
namespace :query_trace do
desc "Enables the query_trace plugin. Must restart server to take effect."
task :on => :environment do
unless File.exist?("#{RAILS_ROOT}/vendor/query_trace.tar.gz")
Dir.chdir("#{RAILS_ROOT}/vendor") do
url = "https://terralien.devguard.com/svn/projects/plugins/query_trace"
puts "Loading query_trace from #{url}..."
system "svn co #{url} query_trace"
system "tar zcf query_trace.tar.gz --exclude=.svn query_trace"
FileUtils.rm_rf("query_trace")
end
end
Dir.chdir("#{RAILS_ROOT}/vendor/plugins") do
system "tar zxf ../query_trace.tar.gz query_trace"
end
puts "QueryTrace plugin enabled. Must restart server to take effect."
end
desc "Disables the query_trace plugin. Must restart server to take effect."
task :off => :environment do
FileUtils.rm_rf("#{RAILS_ROOT}/vendor/plugins/query_trace")
puts "QueryTrace plugin disabled. Must restart server to take effect."
end
end
namespace :query_analyzer do
desc "Enables the query_analyzer plugin. Must restart server to take effect."
task :on => :environment do
unless File.exist?("#{RAILS_ROOT}/vendor/query_analyzer.tar.gz")
Dir.chdir("#{RAILS_ROOT}/vendor") do
url = "http://svn.nfectio.us/plugins/query_analyzer"
puts "Loading query_analyzer from #{url}..."
system "svn co #{url} query_analyzer"
system "tar zcf query_analyzer.tar.gz --exclude=.svn query_analyzer"
FileUtils.rm_rf("query_analyzer")
end
end
Dir.chdir("#{RAILS_ROOT}/vendor/plugins") do
system "tar zxf ../query_analyzer.tar.gz query_analyzer"
end
puts "QueryAnalyzer plugin enabled. Must restart server to take effect."
end
desc "Disables the query_analyzer plugin. Must restart server to take effect."
task :off => :environment do
FileUtils.rm_rf("#{RAILS_ROOT}/vendor/plugins/query_analyzer")
puts "QueryAnalyzer plugin disabled. Must restart server to take effect."
end
end

View file

@ -1,23 +0,0 @@
namespace :tracks do
desc 'Replace the password of USER with a new one.'
task :password => :environment do
require "highline/import"
user = User.find_by_login(ENV['USER'])
if user.nil?
puts "Sorry, we couldn't find user '#{ENV['USER']}'. To specify a different user, pass USER=username to this task."
exit 0
end
puts "Changing Tracks password for #{ENV['USER']}."
password = ask("New password: ") { |q| q.echo = false }
password_confirmation = ask('Retype new password: ') { |q| q.echo = false }
begin
user.change_password(password, password_confirmation)
rescue ActiveRecord::RecordInvalid
puts "Sorry, we couldn't change #{ENV['USER']}'s password: "
user.errors.each_full { |msg| puts "- #{msg}\n" }
end
end
end

View file

@ -1,144 +0,0 @@
gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9
rspec_gem_dir = nil
Dir["#{RAILS_ROOT}/vendor/gems/*"].each do |subdir|
rspec_gem_dir = subdir if subdir.gsub("#{RAILS_ROOT}/vendor/gems/","") =~ /^(\w+-)?rspec-(\d+)/ && File.exist?("#{subdir}/lib/spec/rake/spectask.rb")
end
rspec_plugin_dir = File.expand_path(File.dirname(__FILE__) + '/../../vendor/plugins/rspec')
if rspec_gem_dir && (test ?d, rspec_plugin_dir)
raise "\n#{'*'*50}\nYou have rspec installed in both vendor/gems and vendor/plugins\nPlease pick one and dispose of the other.\n#{'*'*50}\n\n"
end
if rspec_gem_dir
$LOAD_PATH.unshift("#{rspec_gem_dir}/lib")
elsif File.exist?(rspec_plugin_dir)
$LOAD_PATH.unshift("#{rspec_plugin_dir}/lib")
end
# Don't load rspec if running "rake gems:*"
unless ARGV.any? {|a| a =~ /^gems/}
begin
require 'spec/rake/spectask'
rescue MissingSourceFile
module Spec
module Rake
class SpecTask
def initialize(name)
task name do
# if rspec-rails is a configured gem, this will output helpful material and exit ...
require File.expand_path(File.join(File.dirname(__FILE__),"..","..","config","environment"))
# ... otherwise, do this:
raise <<-MSG
#{"*" * 80}
* You are trying to run an rspec rake task defined in
* #{__FILE__},
* but rspec can not be found in vendor/gems, vendor/plugins or system gems.
#{"*" * 80}
MSG
end
end
end
end
end
end
Rake.application.instance_variable_get('@tasks').delete('default')
spec_prereq = File.exist?(File.join(RAILS_ROOT, 'config', 'database.yml')) ? "db:test:prepare" : :noop
task :noop do
end
task :default => :spec
task :stats => "spec:statsetup"
desc "Run all specs in spec directory (excluding plugin specs)"
Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t|
t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
t.spec_files = FileList['spec/**/*_spec.rb']
end
namespace :spec do
desc "Run all specs in spec directory with RCov (excluding plugin specs)"
Spec::Rake::SpecTask.new(:rcov) do |t|
t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
t.spec_files = FileList['spec/**/*_spec.rb']
t.rcov = true
t.rcov_opts = lambda do
IO.readlines("#{RAILS_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
end
end
desc "Print Specdoc for all specs (excluding plugin specs)"
Spec::Rake::SpecTask.new(:doc) do |t|
t.spec_opts = ["--format", "specdoc", "--dry-run"]
t.spec_files = FileList['spec/**/*_spec.rb']
end
desc "Print Specdoc for all plugin examples"
Spec::Rake::SpecTask.new(:plugin_doc) do |t|
t.spec_opts = ["--format", "specdoc", "--dry-run"]
t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*')
end
[:models, :controllers, :views, :helpers, :lib, :integration].each do |sub|
desc "Run the code examples in spec/#{sub}"
Spec::Rake::SpecTask.new(sub => spec_prereq) do |t|
t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
end
end
desc "Run the code examples in vendor/plugins (except RSpec's own)"
Spec::Rake::SpecTask.new(:plugins => spec_prereq) do |t|
t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*').exclude("vendor/plugins/rspec-rails/*")
end
namespace :plugins do
desc "Runs the examples for rspec_on_rails"
Spec::Rake::SpecTask.new(:rspec_on_rails) do |t|
t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
t.spec_files = FileList['vendor/plugins/rspec-rails/spec/**/*_spec.rb']
end
end
# Setup specs for stats
task :statsetup do
require 'code_statistics'
::STATS_DIRECTORIES << %w(Model\ specs spec/models) if File.exist?('spec/models')
::STATS_DIRECTORIES << %w(View\ specs spec/views) if File.exist?('spec/views')
::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers) if File.exist?('spec/controllers')
::STATS_DIRECTORIES << %w(Helper\ specs spec/helpers) if File.exist?('spec/helpers')
::STATS_DIRECTORIES << %w(Library\ specs spec/lib) if File.exist?('spec/lib')
::STATS_DIRECTORIES << %w(Routing\ specs spec/routing) if File.exist?('spec/routing')
::STATS_DIRECTORIES << %w(Integration\ specs spec/integration) if File.exist?('spec/integration')
::CodeStatistics::TEST_TYPES << "Model specs" if File.exist?('spec/models')
::CodeStatistics::TEST_TYPES << "View specs" if File.exist?('spec/views')
::CodeStatistics::TEST_TYPES << "Controller specs" if File.exist?('spec/controllers')
::CodeStatistics::TEST_TYPES << "Helper specs" if File.exist?('spec/helpers')
::CodeStatistics::TEST_TYPES << "Library specs" if File.exist?('spec/lib')
::CodeStatistics::TEST_TYPES << "Routing specs" if File.exist?('spec/routing')
::CodeStatistics::TEST_TYPES << "Integration specs" if File.exist?('spec/integration')
end
namespace :db do
namespace :fixtures do
desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z."
task :load => :environment do
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = File.join(Rails.root, 'spec', 'fixtures')
fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
require 'active_record/fixtures'
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file|
Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*'))
end
end
end
end
end
end

View file

@ -1,15 +0,0 @@
desc "Initialises the installation, copy the *.tmpl files and directories to versions named without the .tmpl extension. It won't overwrite the files and directories if you've already copied them. You need to manually copy database.yml.tmpl -> database.yml and fill in the details before you run this task."
task :setup_tracks => :environment do
# Check the root directory for template files
FileList["*.tmpl"].each do |template_file|
f = File.basename(template_file) # with suffix
f_only = File.basename(template_file,".tmpl") # without suffix
if File.exists?(f_only)
puts f_only + " already exists"
else
cp_r(f, f_only)
puts f_only + " created"
end
end
end

View file

@ -1,40 +0,0 @@
desc "Updates sqlite/sqlite3 databases created under Tracks 1.03 to the format required for Tracks 1.04. After this is done, you should be able to keep up to date with changes in the schema by running rake db:migrate."
task :upgrade_sqlite_db => :environment do
# Change the three lines below appropriately for your setup
old_db = "tracks_103.db"
new_db = "tracks_104.db"
cmd = "sqlite3"
replace_string = "update todos set done='f' where done=0;\nupdate todos set done='t' where done=1;\nupdate contexts set hide='f' where hide=0;\nupdate contexts set hide='t' where hide=1;\nupdate projects set done='f' where done=0;\nupdate projects set done='t' where done=1;\nCREATE TABLE 'schema_info' (\n 'version' INTEGER default NULL\n);\nINSERT INTO \"schema_info\" VALUES(1);\nCOMMIT;"
# cd to the db directory
cd("db") do
# Dump the old db into the temp file and replace the tinyints with booleans
`#{cmd} #{old_db} .dump | sed "s/tinyint(4) NOT NULL default '0'/boolean default 'f'/" > temp.sql`
# Create a second sqldump file for writing
sqldump = File.open("temp2.sql", "w+")
File.open("temp.sql") do |file|
file.each_line do |line|
# If COMMIT is on the line, insert the replace string
# else just write the line back in
# This effectively replaces COMMIT with the replace string
if /COMMIT/ =~ line
sqldump.write replace_string
else
sqldump.write line
end
end
sqldump.close
end
# Read the second dump back in to a new db
system "#{cmd} #{new_db} < temp2.sql"
puts "Created the a new database called #{new_db}."
# Clean up the temp files
rm("temp.sql")
rm("temp2.sql")
puts "Temporary files cleaned up."
end
# rake db:migrate
puts "Now check the database and run 'rake db:migrate' in the root of your Tracks installation."
end

View file

@ -1,27 +0,0 @@
module Tracks
class Config
def self.salt
SITE_CONFIG['salt']
end
def self.auth_schemes
SITE_CONFIG['authentication_schemes'] || []
end
def self.openid_enabled?
auth_schemes.include?('open_id')
end
def self.cas_enabled?
auth_schemes.include?('cas')
end
def self.prefered_auth?
if SITE_CONFIG['prefered_auth']
SITE_CONFIG['prefered_auth']
else
auth_schemes.first
end
end
end
end

View file

@ -1,68 +0,0 @@
# Inspiration from Bruce Williams [http://codefluency.com/articles/2006/07/01/rails-views-getting-in-context/]
module Tracks
module SourceViewSwitching
class Responder
def initialize(source_view)
@source_view = source_view.underscore.gsub(/\s+/,'_').to_sym rescue nil
end
def nil?
yield if @source_view.nil? && block_given?
end
def context
yield if :context == @source_view && block_given?
end
def method_missing(check_source_view,*args)
yield if check_source_view == @source_view && block_given?
end
end
module Controller
def self.included(base)
base.send(:helper, Tracks::SourceViewSwitching::Helper)
base.send(:helper_method, :source_view)
end
def source_view_is( s )
s == (params[:_source_view] || @source_view).to_sym
end
def source_view_is_one_of( *s )
s.include?(params[:_source_view].to_sym)
end
def source_view
responder = Tracks::SourceViewSwitching::Responder.new(params[:_source_view] || @source_view)
block_given? ? yield(responder) : responder
end
end
module Helper
def source_view_tag(name)
hidden_field_tag :_source_view, name.underscore.gsub(/\s+/,'_')
end
def source_view_is( s )
s == (params[:_source_view] || @source_view).to_sym
end
def source_view_is_one_of( *s )
s.include?(params[:_source_view].to_sym)
end
end
end
end
ActionController::Base.send(:include, Tracks::SourceViewSwitching::Controller)

View file

@ -1,59 +0,0 @@
module Tracks
module TodoList
# TODO: this module should be deprecated. This could mostly (all?) be replaced by named scopes)
def not_done_todos(opts={})
@not_done_todos ||= self.find_not_done_todos(opts)
end
def done_todos
@done_todos ||= self.find_done_todos
end
def deferred_todos
@deferred_todos ||= self.find_deferred_todos
end
def find_not_done_todos(opts={})
with_not_done_scope(opts) do
self.todos.find(:all, :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC")
end
end
def find_deferred_todos(opts={})
self.todos.find_in_state(:all, :deferred, :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC")
end
def find_done_todos
self.todos.completed.all(:order => "todos.completed_at DESC", :limit => self.user.prefs.show_number_completed)
end
def not_done_todo_count(opts={})
with_not_done_scope(opts) do
self.todos.count
end
end
def with_not_done_scope(opts={})
conditions = ["todos.state = ?", 'active']
if opts.has_key?(:include_project_hidden_todos) && (opts[:include_project_hidden_todos] == true)
conditions = ["(todos.state = ? OR todos.state = ?)", 'active', 'project_hidden']
end
if opts.has_key?(:tag)
conditions = ["todos.state = ? AND taggings.tag_id = ?", 'active', opts[:tag]]
end
self.todos.send :with_scope, :find => {:conditions => conditions, :include => [:taggings]} do
yield
end
end
def done_todo_count
self.todos.count_in_state(:completed)
end
def deferred_todo_count
self.todos.count_in_state(:deferred)
end
end
end

View file

@ -1,107 +0,0 @@
# adapted from https://gist.github.com/471663 and https://rails.lighthouseapp.com/projects/8994/tickets/4690-mongrel-doesnt-work-with-rails-238
def check_mongrel_around_115
begin
# Gem.available? is deprecated from rubygems 1.8.2
Gem::Specification::find_by_name "mongrel", "~>1.1.5"
rescue
if RUBY_VERSION[2] == "9"
false
else
Gem.available?('mongrel', '~>1.1.5')
end
end
end
mongrel115 = check_mongrel_around_115
if Rails.version == '2.3.14' && mongrel115 && self.class.const_defined?(:Mongrel)
# Pulled right from latest rack. Old looked like this in 1.1.0 version.
#
# def [](k)
# super(@names[k] ||= @names[k.downcase])
# end
#
module Rack
module Utils
class HeaderHash < Hash
def [](k)
super(@names[k]) if @names[k]
super(@names[k.downcase])
end
end
end
end
# Code pulled from the ticket above.
#
class Mongrel::CGIWrapper
def header_with_rails_fix(options = 'text/html')
@head['cookie'] = options.delete('cookie').flatten.map { |v| v.sub(/^\n/,'') } if options.class != String and options['cookie']
header_without_rails_fix(options)
end
alias_method_chain :header, :rails_fix
end
# Pulled right from 2.3.10 ActionPack. Simple diff was
#
# if headers.include?('Set-Cookie')
# headers['cookie'] = headers.delete('Set-Cookie').split("\n")
# end
#
# to
#
# if headers['Set-Cookie']
# headers['cookie'] = headers.delete('Set-Cookie').split("\n")
# end
#
module ActionController
class CGIHandler
def self.dispatch_cgi(app, cgi, out = $stdout)
env = cgi.__send__(:env_table)
env.delete "HTTP_CONTENT_LENGTH"
cgi.stdinput.extend ProperStream
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({
"rack.version" => [0,1],
"rack.input" => cgi.stdinput,
"rack.errors" => $stderr,
"rack.multithread" => false,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
})
env["QUERY_STRING"] ||= ""
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["REQUEST_PATH"] ||= "/"
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
status, headers, body = app.call(env)
begin
out.binmode if out.respond_to?(:binmode)
out.sync = false if out.respond_to?(:sync=)
headers['Status'] = status.to_s
if headers['Set-Cookie']
headers['cookie'] = headers.delete('Set-Cookie').split("\n")
end
out.write(cgi.header(headers))
body.each { |part|
out.write part
out.flush if out.respond_to?(:flush)
}
ensure
body.close if body.respond_to?(:close)
end
end
end
end
end

View file

@ -1,21 +0,0 @@
# Be sure to restart your server when you modify this file.
# These settings change the behavior of Rails 2 apps and will be defaults
# for Rails 3. You can remove this initializer when Rails 3 is released.
if defined?(ActiveRecord)
# Include Active Record class name as root for JSON serialized output.
ActiveRecord::Base.include_root_in_json = true
# Store the full class name (including module namespace) in STI type column.
ActiveRecord::Base.store_full_sti_class = true
end
ActionController::Routing.generate_best_match = false
# Use ISO 8601 format for JSON serialized times and dates.
ActiveSupport.use_standard_json_time_format = true
# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
# if you're including raw json in an HTML page.
ActiveSupport.escape_html_entities_in_json = false

View file

@ -1,2 +0,0 @@
require 'extra_validations'
ActiveRecord::Base.extend ExtraValidations

View file

@ -1,29 +0,0 @@
module ExtraValidations
# Validates the value of the specified attribute by checking for a forbidden string
#
# class Person < ActiveRecord::Base
# validates_does_not_contain :first_name, :string => ','
# end
#
# A string must be provided or else an exception will be raised.
#
# Configuration options:
# * <tt>message</tt> - A custom error message (default is: "is invalid")
# * <tt>string</tt> - The string to verify is not included (note: must be supplied!)
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_does_not_contain(*attr_names)
configuration = { :message => I18n.translate('activerecord.errors.messages')[:invalid], :on => :save, :string => nil }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
raise(ArgumentError, "A string must be supplied as the :string option of the configuration hash") unless configuration[:string].is_a?(String)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message]) if value.to_s =~ Regexp.new(Regexp.escape(configuration[:string]))
end
end
end

View file

@ -1,35 +0,0 @@
* Fake HTTP method from OpenID server since they only support a GET. Eliminates the need to set an extra route to match the server's reply. [Josh Peek]
* OpenID 2.0 recommends that forms should use the field name "openid_identifier" rather than "openid_url" [Josh Peek]
* Return open_id_response.display_identifier to the application instead of .endpoints.claimed_id. [nbibler]
* Add Timeout protection [Rick]
* An invalid identity url passed through authenticate_with_open_id will no longer raise an InvalidOpenId exception. Instead it will return Result[:missing] to the completion block.
* Allow a return_to option to be used instead of the requested url [Josh Peek]
* Updated plugin to use Ruby OpenID 2.x.x [Josh Peek]
* Tied plugin to ruby-openid 1.1.4 gem until we can make it compatible with 2.x [DHH]
* Use URI instead of regexps to normalize the URL and gain free, better matching #8136 [dkubb]
* Allow -'s in #normalize_url [Rick]
* remove instance of mattr_accessor, it was breaking tests since they don't load ActiveSupport. Fix Timeout test [Rick]
* Throw a InvalidOpenId exception instead of just a RuntimeError when the URL can't be normalized [DHH]
* Just use the path for the return URL, so extra query parameters don't interfere [DHH]
* Added a new default database-backed store after experiencing trouble with the filestore on NFS. The file store is still available as an option [DHH]
* Added normalize_url and applied it to all operations going through the plugin [DHH]
* Removed open_id? as the idea of using the same input box for both OpenID and username has died -- use using_open_id? instead (which checks for the presence of params[:openid_url] by default) [DHH]
* Added OpenIdAuthentication::Result to make it easier to deal with default situations where you don't care to do something particular for each error state [DHH]
* Stop relying on root_url being defined, we can just grab the current url instead [DHH]

View file

@ -1,231 +0,0 @@
OpenIdAuthentication
====================
Provides a thin wrapper around the excellent ruby-openid gem from JanRan. Be sure to install that first:
gem install ruby-openid
To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb
from that gem.
The specification used is http://openid.net/specs/openid-authentication-2_0.html.
Prerequisites
=============
OpenID authentication uses the session, so be sure that you haven't turned that off. It also relies on a number of
database tables to store the authentication keys. So you'll have to run the migration to create these before you get started:
rake open_id_authentication:db:create
Or, use the included generators to install or upgrade:
./script/generate open_id_authentication_tables MigrationName
./script/generate upgrade_open_id_authentication_tables MigrationName
Alternatively, you can use the file-based store, which just relies on on tmp/openids being present in RAILS_ROOT. But be aware that this store only works if you have a single application server. And it's not safe to use across NFS. It's recommended that you use the database store if at all possible. To use the file-based store, you'll also have to add this line to your config/environment.rb:
OpenIdAuthentication.store = :file
This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations.
If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb.
The plugin also expects to find a root_url method that points to the home page of your site. You can accomplish this by using a root route in config/routes.rb:
map.root :controller => 'articles'
This plugin relies on Rails Edge revision 6317 or newer.
Example
=======
This example is just to meant to demonstrate how you could use OpenID authentication. You might well want to add
salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point,
not a destination.
Note that the User model referenced in the simple example below has an 'identity_url' attribute. You will want to add the same or similar field to whatever
model you are using for authentication.
Also of note is the following code block used in the example below:
authenticate_with_open_id do |result, identity_url|
...
end
In the above code block, 'identity_url' will need to match user.identity_url exactly. 'identity_url' will be a string in the form of 'http://example.com' -
If you are storing just 'example.com' with your user, the lookup will fail.
There is a handy method in this plugin called 'normalize_url' that will help with validating OpenID URLs.
OpenIdAuthentication.normalize_url(user.identity_url)
The above will return a standardized version of the OpenID URL - the above called with 'example.com' will return 'http://example.com/'
It will also raise an InvalidOpenId exception if the URL is determined to not be valid.
Use the above code in your User model and validate OpenID URLs before saving them.
config/routes.rb
map.root :controller => 'articles'
map.resource :session
app/views/sessions/new.erb
<% form_tag(session_url) do %>
<p>
<label for="name">Username:</label>
<%= text_field_tag "name" %>
</p>
<p>
<label for="password">Password:</label>
<%= password_field_tag %>
</p>
<p>
...or use:
</p>
<p>
<label for="openid_identifier">OpenID:</label>
<%= text_field_tag "openid_identifier" %>
</p>
<p>
<%= submit_tag 'Sign in', :disable_with => "Signing in&hellip;" %>
</p>
<% end %>
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def create
if using_open_id?
open_id_authentication
else
password_authentication(params[:name], params[:password])
end
end
protected
def password_authentication(name, password)
if @current_user = @account.users.authenticate(params[:name], params[:password])
successful_login
else
failed_login "Sorry, that username/password doesn't work"
end
end
def open_id_authentication
authenticate_with_open_id do |result, identity_url|
if result.successful?
if @current_user = @account.users.find_by_identity_url(identity_url)
successful_login
else
failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
end
else
failed_login result.message
end
end
end
private
def successful_login
session[:user_id] = @current_user.id
redirect_to(root_url)
end
def failed_login(message)
flash[:error] = message
redirect_to(new_session_url)
end
end
If you're fine with the result messages above and don't need individual logic on a per-failure basis,
you can collapse the case into a mere boolean:
def open_id_authentication
authenticate_with_open_id do |result, identity_url|
if result.successful? && @current_user = @account.users.find_by_identity_url(identity_url)
successful_login
else
failed_login(result.message || "Sorry, no user by that identity URL exists (#{identity_url})")
end
end
end
Simple Registration OpenID Extension
====================================
Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension
You can support it in your app by changing #open_id_authentication
def open_id_authentication(identity_url)
# Pass optional :required and :optional keys to specify what sreg fields you want.
# Be sure to yield registration, a third argument in the #authenticate_with_open_id block.
authenticate_with_open_id(identity_url,
:required => [ :nickname, :email ],
:optional => :fullname) do |result, identity_url, registration|
case result.status
when :missing
failed_login "Sorry, the OpenID server couldn't be found"
when :invalid
failed_login "Sorry, but this does not appear to be a valid OpenID"
when :canceled
failed_login "OpenID verification was canceled"
when :failed
failed_login "Sorry, the OpenID verification failed"
when :successful
if @current_user = @account.users.find_by_identity_url(identity_url)
assign_registration_attributes!(registration)
if current_user.save
successful_login
else
failed_login "Your OpenID profile registration failed: " +
@current_user.errors.full_messages.to_sentence
end
else
failed_login "Sorry, no user by that identity URL exists"
end
end
end
end
# registration is a hash containing the valid sreg keys given above
# use this to map them to fields of your user model
def assign_registration_attributes!(registration)
model_to_registration_mapping.each do |model_attribute, registration_attribute|
unless registration[registration_attribute].blank?
@current_user.send("#{model_attribute}=", registration[registration_attribute])
end
end
end
def model_to_registration_mapping
{ :login => 'nickname', :email => 'email', :display_name => 'fullname' }
end
Attribute Exchange OpenID Extension
===================================
Some OpenID providers also support the OpenID AX (attribute exchange) protocol for exchanging identity information between endpoints. See more: http://openid.net/specs/openid-attribute-exchange-1_0.html
Accessing AX data is very similar to the Simple Registration process, described above -- just add the URI identifier for the AX field to your :optional or :required parameters. For example:
authenticate_with_open_id(identity_url,
:required => [ :email, 'http://schema.openid.net/birthDate' ]) do |result, identity_url, registration|
This would provide the sreg data for :email, and the AX data for 'http://schema.openid.net/birthDate'
Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license

View file

@ -1,22 +0,0 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
desc 'Default: run unit tests.'
task :default => :test
desc 'Test the open_id_authentication plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
desc 'Generate documentation for the open_id_authentication plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'OpenIdAuthentication'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end

View file

@ -1,11 +0,0 @@
class OpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
def initialize(runtime_args, runtime_options = {})
super
end
def manifest
record do |m|
m.migration_template 'migration.rb', 'db/migrate'
end
end
end

View file

@ -1,20 +0,0 @@
class <%= class_name %> < ActiveRecord::Migration
def self.up
create_table :open_id_authentication_associations, :force => true do |t|
t.integer :issued, :lifetime
t.string :handle, :assoc_type
t.binary :server_url, :secret
end
create_table :open_id_authentication_nonces, :force => true do |t|
t.integer :timestamp, :null => false
t.string :server_url, :null => true
t.string :salt, :null => false
end
end
def self.down
drop_table :open_id_authentication_associations
drop_table :open_id_authentication_nonces
end
end

View file

@ -1,26 +0,0 @@
class <%= class_name %> < ActiveRecord::Migration
def self.up
drop_table :open_id_authentication_settings
drop_table :open_id_authentication_nonces
create_table :open_id_authentication_nonces, :force => true do |t|
t.integer :timestamp, :null => false
t.string :server_url, :null => true
t.string :salt, :null => false
end
end
def self.down
drop_table :open_id_authentication_nonces
create_table :open_id_authentication_nonces, :force => true do |t|
t.integer :created
t.string :nonce
end
create_table :open_id_authentication_settings, :force => true do |t|
t.string :setting
t.binary :value
end
end
end

View file

@ -1,11 +0,0 @@
class UpgradeOpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
def initialize(runtime_args, runtime_options = {})
super
end
def manifest
record do |m|
m.migration_template 'migration.rb', 'db/migrate'
end
end
end

View file

@ -1,18 +0,0 @@
if config.respond_to?(:gems)
config.gem 'ruby-openid', :lib => 'openid', :version => '>=2.0.4'
else
begin
require 'openid'
rescue LoadError
begin
gem 'ruby-openid', '>=2.0.4'
rescue Gem::LoadError
puts "Install the ruby-openid gem to enable OpenID support"
end
end
end
config.to_prepare do
OpenID::Util.logger = Rails.logger
ActionController::Base.send :include, OpenIdAuthentication
end

View file

@ -1,240 +0,0 @@
require 'uri'
require 'openid/extensions/sreg'
require 'openid/extensions/ax'
require 'openid/store/filesystem'
require File.dirname(__FILE__) + '/open_id_authentication/association'
require File.dirname(__FILE__) + '/open_id_authentication/nonce'
require File.dirname(__FILE__) + '/open_id_authentication/db_store'
require File.dirname(__FILE__) + '/open_id_authentication/request'
require File.dirname(__FILE__) + '/open_id_authentication/timeout_fixes' if OpenID::VERSION == "2.0.4"
module OpenIdAuthentication
OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
def self.store
@@store
end
def self.store=(*store_option)
store, *parameters = *([ store_option ].flatten)
@@store = case store
when :db
OpenIdAuthentication::DbStore.new
when :file
OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
else
store
end
end
self.store = :db
class InvalidOpenId < StandardError
end
class Result
ERROR_MESSAGES = {
:missing => "Sorry, the OpenID server couldn't be found",
:invalid => "Sorry, but this does not appear to be a valid OpenID",
:canceled => "OpenID verification was canceled",
:failed => "OpenID verification failed",
:setup_needed => "OpenID verification needs setup"
}
def self.[](code)
new(code)
end
def initialize(code)
@code = code
end
def status
@code
end
ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
def successful?
@code == :successful
end
def unsuccessful?
ERROR_MESSAGES.keys.include?(@code)
end
def message
ERROR_MESSAGES[@code]
end
end
# normalizes an OpenID according to http://openid.net/specs/openid-authentication-2_0.html#normalization
def self.normalize_identifier(identifier)
# clean up whitespace
identifier = identifier.to_s.strip
# if an XRI has a prefix, strip it.
identifier.gsub!(/xri:\/\//i, '')
# dodge XRIs -- TODO: validate, don't just skip.
unless ['=', '@', '+', '$', '!', '('].include?(identifier.at(0))
# does it begin with http? if not, add it.
identifier = "http://#{identifier}" unless identifier =~ /^http/i
# strip any fragments
identifier.gsub!(/\#(.*)$/, '')
begin
uri = URI.parse(identifier)
uri.scheme = uri.scheme.downcase # URI should do this
identifier = uri.normalize.to_s
rescue URI::InvalidURIError
raise InvalidOpenId.new("#{identifier} is not an OpenID identifier")
end
end
return identifier
end
# deprecated for OpenID 2.0, where not all OpenIDs are URLs
def self.normalize_url(url)
ActiveSupport::Deprecation.warn "normalize_url has been deprecated, use normalize_identifier instead"
self.normalize_identifier(url)
end
protected
def normalize_url(url)
OpenIdAuthentication.normalize_url(url)
end
def normalize_identifier(url)
OpenIdAuthentication.normalize_identifier(url)
end
# The parameter name of "openid_identifier" is used rather than the Rails convention "open_id_identifier"
# because that's what the specification dictates in order to get browser auto-complete working across sites
def using_open_id?(identity_url = nil) #:doc:
identity_url ||= params[:openid_identifier] || params[:openid_url]
!identity_url.blank? || params[:open_id_complete]
end
def authenticate_with_open_id(identity_url = nil, options = {}, &block) #:doc:
identity_url ||= params[:openid_identifier] || params[:openid_url]
if params[:open_id_complete].nil?
begin_open_id_authentication(identity_url, options, &block)
else
complete_open_id_authentication(&block)
end
end
private
def begin_open_id_authentication(identity_url, options = {})
identity_url = normalize_identifier(identity_url)
return_to = options.delete(:return_to)
method = options.delete(:method)
options[:required] ||= [] # reduces validation later
options[:optional] ||= []
open_id_request = open_id_consumer.begin(identity_url)
add_simple_registration_fields(open_id_request, options)
add_ax_fields(open_id_request, options)
redirect_to(open_id_redirect_url(open_id_request, return_to, method))
rescue OpenIdAuthentication::InvalidOpenId => e
yield Result[:invalid], identity_url, nil
rescue OpenID::OpenIDError, Timeout::Error => e
logger.error("[OPENID] #{e}")
yield Result[:missing], identity_url, nil
end
def complete_open_id_authentication
params_with_path = params.reject { |key, value| request.path_parameters[key] }
params_with_path.delete(:format)
open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
identity_url = normalize_identifier(open_id_response.display_identifier) if open_id_response.display_identifier
case open_id_response.status
when OpenID::Consumer::SUCCESS
profile_data = {}
# merge the SReg data and the AX data into a single hash of profile data
[ OpenID::SReg::Response, OpenID::AX::FetchResponse ].each do |data_response|
if data_response.from_success_response( open_id_response )
profile_data.merge! data_response.from_success_response( open_id_response ).data
end
end
yield Result[:successful], identity_url, profile_data
when OpenID::Consumer::CANCEL
yield Result[:canceled], identity_url, nil
when OpenID::Consumer::FAILURE
yield Result[:failed], identity_url, nil
when OpenID::Consumer::SETUP_NEEDED
yield Result[:setup_needed], open_id_response.setup_url, nil
end
end
def open_id_consumer
OpenID::Consumer.new(session, OpenIdAuthentication.store)
end
def add_simple_registration_fields(open_id_request, fields)
sreg_request = OpenID::SReg::Request.new
# filter out AX identifiers (URIs)
required_fields = fields[:required].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
optional_fields = fields[:optional].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
sreg_request.request_fields(required_fields, true) unless required_fields.blank?
sreg_request.request_fields(optional_fields, false) unless optional_fields.blank?
sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
open_id_request.add_extension(sreg_request)
end
def add_ax_fields( open_id_request, fields )
ax_request = OpenID::AX::FetchRequest.new
# look through the :required and :optional fields for URIs (AX identifiers)
fields[:required].each do |f|
next unless f =~ /^https?:\/\//
ax_request.add( OpenID::AX::AttrInfo.new( f, nil, true ) )
end
fields[:optional].each do |f|
next unless f =~ /^https?:\/\//
ax_request.add( OpenID::AX::AttrInfo.new( f, nil, false ) )
end
open_id_request.add_extension( ax_request )
end
def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
open_id_request.return_to_args['_method'] = (method || request.method).to_s
open_id_request.return_to_args['open_id_complete'] = '1'
open_id_request.redirect_url(root_url, return_to || requested_url)
end
def requested_url
relative_url_root = self.class.respond_to?(:relative_url_root) ?
self.class.relative_url_root.to_s :
request.relative_url_root
"#{request.protocol}#{request.host_with_port}#{ActionController::Base.relative_url_root}#{request.path}"
end
def timeout_protection_from_identity_server
yield
rescue Timeout::Error
Class.new do
def status
OpenID::FAILURE
end
def msg
"Identity server timed out"
end
end.new
end
end

View file

@ -1,9 +0,0 @@
module OpenIdAuthentication
class Association < ActiveRecord::Base
set_table_name :open_id_authentication_associations
def from_record
OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
end
end
end

View file

@ -1,55 +0,0 @@
require 'openid/store/interface'
module OpenIdAuthentication
class DbStore < OpenID::Store::Interface
def self.cleanup_nonces
now = Time.now.to_i
Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew])
end
def self.cleanup_associations
now = Time.now.to_i
Association.delete_all(['issued + lifetime > ?',now])
end
def store_association(server_url, assoc)
remove_association(server_url, assoc.handle)
Association.create(:server_url => server_url,
:handle => assoc.handle,
:secret => assoc.secret,
:issued => assoc.issued,
:lifetime => assoc.lifetime,
:assoc_type => assoc.assoc_type)
end
def get_association(server_url, handle = nil)
assocs = if handle.blank?
Association.find_all_by_server_url(server_url)
else
Association.find_all_by_server_url_and_handle(server_url, handle)
end
assocs.reverse.each do |assoc|
a = assoc.from_record
if a.expires_in == 0
assoc.destroy
else
return a
end
end if assocs.any?
return nil
end
def remove_association(server_url, handle)
Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0
end
def use_nonce(server_url, timestamp, salt)
return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt)
return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
Nonce.create(:server_url => server_url, :timestamp => timestamp, :salt => salt)
return true
end
end
end

View file

@ -1,5 +0,0 @@
module OpenIdAuthentication
class Nonce < ActiveRecord::Base
set_table_name :open_id_authentication_nonces
end
end

View file

@ -1,23 +0,0 @@
module OpenIdAuthentication
module Request
def self.included(base)
base.alias_method_chain :request_method, :openid
end
def request_method_with_openid
if !parameters[:_method].blank? && parameters[:open_id_complete] == '1'
parameters[:_method].to_sym
else
request_method_without_openid
end
end
end
end
# In Rails 2.3, the request object has been renamed
# from AbstractRequest to Request
if defined? ActionController::Request
ActionController::Request.send :include, OpenIdAuthentication::Request
else
ActionController::AbstractRequest.send :include, OpenIdAuthentication::Request
end

View file

@ -1,20 +0,0 @@
# http://trac.openidenabled.com/trac/ticket/156
module OpenID
@@timeout_threshold = 20
def self.timeout_threshold
@@timeout_threshold
end
def self.timeout_threshold=(value)
@@timeout_threshold = value
end
class StandardFetcher
def make_http(uri)
http = @proxy.new(uri.host, uri.port)
http.read_timeout = http.open_timeout = OpenID.timeout_threshold
http
end
end
end

View file

@ -1,30 +0,0 @@
namespace :open_id_authentication do
namespace :db do
desc "Creates authentication tables for use with OpenIdAuthentication"
task :create => :environment do
generate_migration(["open_id_authentication_tables", "add_open_id_authentication_tables"])
end
desc "Upgrade authentication tables from ruby-openid 1.x.x to 2.x.x"
task :upgrade => :environment do
generate_migration(["upgrade_open_id_authentication_tables", "upgrade_open_id_authentication_tables"])
end
def generate_migration(args)
require 'rails_generator'
require 'rails_generator/scripts/generate'
if ActiveRecord::Base.connection.supports_migrations?
Rails::Generator::Scripts::Generate.new.run(args)
else
raise "Task unavailable to this database (no migration support)"
end
end
desc "Clear the authentication tables"
task :clear => :environment do
OpenIdAuthentication::DbStore.cleanup_nonces
OpenIdAuthentication::DbStore.cleanup_associations
end
end
end

View file

@ -1,32 +0,0 @@
require File.dirname(__FILE__) + '/test_helper'
class NormalizeTest < Test::Unit::TestCase
include OpenIdAuthentication
NORMALIZATIONS = {
"openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
"http://openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
"https://openid.aol.com/nextangler" => "https://openid.aol.com/nextangler",
"HTTP://OPENID.AOL.COM/NEXTANGLER" => "http://openid.aol.com/NEXTANGLER",
"HTTPS://OPENID.AOL.COM/NEXTANGLER" => "https://openid.aol.com/NEXTANGLER",
"loudthinking.com" => "http://loudthinking.com/",
"http://loudthinking.com" => "http://loudthinking.com/",
"http://loudthinking.com:80" => "http://loudthinking.com/",
"https://loudthinking.com:443" => "https://loudthinking.com/",
"http://loudthinking.com:8080" => "http://loudthinking.com:8080/",
"techno-weenie.net" => "http://techno-weenie.net/",
"http://techno-weenie.net" => "http://techno-weenie.net/",
"http://techno-weenie.net " => "http://techno-weenie.net/",
"=name" => "=name"
}
def test_normalizations
NORMALIZATIONS.each do |from, to|
assert_equal to, normalize_identifier(from)
end
end
def test_broken_open_id
assert_raises(InvalidOpenId) { normalize_identifier(nil) }
end
end

View file

@ -1,46 +0,0 @@
require File.dirname(__FILE__) + '/test_helper'
class OpenIdAuthenticationTest < Test::Unit::TestCase
def setup
@controller = Class.new do
include OpenIdAuthentication
def params() {} end
end.new
end
def test_authentication_should_fail_when_the_identity_server_is_missing
open_id_consumer = mock()
open_id_consumer.expects(:begin).raises(OpenID::OpenIDError)
@controller.expects(:open_id_consumer).returns(open_id_consumer)
@controller.expects(:logger).returns(mock(:error => true))
@controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
assert result.missing?
assert_equal "Sorry, the OpenID server couldn't be found", result.message
end
end
def test_authentication_should_be_invalid_when_the_identity_url_is_invalid
@controller.send(:authenticate_with_open_id, "!") do |result, identity_url|
assert result.invalid?, "Result expected to be invalid but was not"
assert_equal "Sorry, but this does not appear to be a valid OpenID", result.message
end
end
def test_authentication_should_fail_when_the_identity_server_times_out
open_id_consumer = mock()
open_id_consumer.expects(:begin).raises(Timeout::Error, "Identity Server took too long.")
@controller.expects(:open_id_consumer).returns(open_id_consumer)
@controller.expects(:logger).returns(mock(:error => true))
@controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
assert result.missing?
assert_equal "Sorry, the OpenID server couldn't be found", result.message
end
end
def test_authentication_should_begin_when_the_identity_server_is_present
@controller.expects(:begin_open_id_authentication)
@controller.send(:authenticate_with_open_id, "http://someone.example.com")
end
end

View file

@ -1,14 +0,0 @@
require File.dirname(__FILE__) + '/test_helper'
class StatusTest < Test::Unit::TestCase
include OpenIdAuthentication
def test_state_conditional
assert Result[:missing].missing?
assert Result[:missing].unsuccessful?
assert !Result[:missing].successful?
assert Result[:successful].successful?
assert !Result[:successful].unsuccessful?
end
end

View file

@ -1,17 +0,0 @@
require 'test/unit'
require 'rubygems'
gem 'activesupport'
require 'active_support'
gem 'actionpack'
require 'action_controller'
gem 'mocha'
require 'mocha'
gem 'ruby-openid'
require 'openid'
RAILS_ROOT = File.dirname(__FILE__) unless defined? RAILS_ROOT
require File.dirname(__FILE__) + "/../lib/open_id_authentication"

View file

@ -1,7 +0,0 @@
ResourceFeeder
==============
Simple feeds for resources
NOTE: This plugin depends on the latest version of simply_helpful, available here:
http://dev.rubyonrails.org/svn/rails/plugins/simply_helpful/

View file

@ -1,22 +0,0 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
desc 'Default: run unit tests.'
task :default => :test
desc 'Test the resource_feed plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
desc 'Generate documentation for the resource_feed plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'ResourceFeed'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end

View file

@ -1,2 +0,0 @@
require 'resource_feeder'
ActionController::Base.send(:include, ResourceFeeder::Rss, ResourceFeeder::Atom)

Some files were not shown because too many files have changed in this diff Show more