##// END OF EJS Templates
Redirected user to where he is coming from after logging hours (#1062)....
Jean-Philippe Lang -
r1339:1a4f81163d2c
parent child
Show More
@@ -1,254 +1,254
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 layout 'base'
19 layout 'base'
20 menu_item :issues
20 menu_item :issues
21 before_filter :find_project, :authorize
21 before_filter :find_project, :authorize
22
22
23 verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
23 verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
24
24
25 helper :sort
25 helper :sort
26 include SortHelper
26 include SortHelper
27 helper :issues
27 helper :issues
28 include TimelogHelper
28 include TimelogHelper
29 helper :custom_fields
29 helper :custom_fields
30 include CustomFieldsHelper
30 include CustomFieldsHelper
31
31
32 def report
32 def report
33 @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
33 @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
34 :klass => Project,
34 :klass => Project,
35 :label => :label_project},
35 :label => :label_project},
36 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
36 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
37 :klass => Version,
37 :klass => Version,
38 :label => :label_version},
38 :label => :label_version},
39 'category' => {:sql => "#{Issue.table_name}.category_id",
39 'category' => {:sql => "#{Issue.table_name}.category_id",
40 :klass => IssueCategory,
40 :klass => IssueCategory,
41 :label => :field_category},
41 :label => :field_category},
42 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
42 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
43 :klass => User,
43 :klass => User,
44 :label => :label_member},
44 :label => :label_member},
45 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
45 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
46 :klass => Tracker,
46 :klass => Tracker,
47 :label => :label_tracker},
47 :label => :label_tracker},
48 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
48 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
49 :klass => Enumeration,
49 :klass => Enumeration,
50 :label => :label_activity},
50 :label => :label_activity},
51 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
51 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
52 :klass => Issue,
52 :klass => Issue,
53 :label => :label_issue}
53 :label => :label_issue}
54 }
54 }
55
55
56 # Add list and boolean custom fields as available criterias
56 # Add list and boolean custom fields as available criterias
57 @project.all_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
57 @project.all_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
58 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM custom_values c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = issues.id)",
58 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM custom_values c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = issues.id)",
59 :format => cf.field_format,
59 :format => cf.field_format,
60 :label => cf.name}
60 :label => cf.name}
61 end
61 end
62
62
63 @criterias = params[:criterias] || []
63 @criterias = params[:criterias] || []
64 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
64 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
65 @criterias.uniq!
65 @criterias.uniq!
66 @criterias = @criterias[0,3]
66 @criterias = @criterias[0,3]
67
67
68 @columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
68 @columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
69
69
70 retrieve_date_range
70 retrieve_date_range
71
71
72 unless @criterias.empty?
72 unless @criterias.empty?
73 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
73 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
74 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
74 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
75
75
76 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
76 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
77 sql << " FROM #{TimeEntry.table_name}"
77 sql << " FROM #{TimeEntry.table_name}"
78 sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
78 sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
79 sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
79 sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
80 sql << " WHERE (%s)" % @project.project_condition(Setting.display_subprojects_issues?)
80 sql << " WHERE (%s)" % @project.project_condition(Setting.display_subprojects_issues?)
81 sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries)
81 sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries)
82 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)]
82 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)]
83 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
83 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
84
84
85 @hours = ActiveRecord::Base.connection.select_all(sql)
85 @hours = ActiveRecord::Base.connection.select_all(sql)
86
86
87 @hours.each do |row|
87 @hours.each do |row|
88 case @columns
88 case @columns
89 when 'year'
89 when 'year'
90 row['year'] = row['tyear']
90 row['year'] = row['tyear']
91 when 'month'
91 when 'month'
92 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
92 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
93 when 'week'
93 when 'week'
94 row['week'] = "#{row['tyear']}-#{row['tweek']}"
94 row['week'] = "#{row['tyear']}-#{row['tweek']}"
95 when 'day'
95 when 'day'
96 row['day'] = "#{row['spent_on']}"
96 row['day'] = "#{row['spent_on']}"
97 end
97 end
98 end
98 end
99
99
100 @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
100 @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
101
101
102 @periods = []
102 @periods = []
103 # Date#at_beginning_of_ not supported in Rails 1.2.x
103 # Date#at_beginning_of_ not supported in Rails 1.2.x
104 date_from = @from.to_time
104 date_from = @from.to_time
105 # 100 columns max
105 # 100 columns max
106 while date_from <= @to.to_time && @periods.length < 100
106 while date_from <= @to.to_time && @periods.length < 100
107 case @columns
107 case @columns
108 when 'year'
108 when 'year'
109 @periods << "#{date_from.year}"
109 @periods << "#{date_from.year}"
110 date_from = (date_from + 1.year).at_beginning_of_year
110 date_from = (date_from + 1.year).at_beginning_of_year
111 when 'month'
111 when 'month'
112 @periods << "#{date_from.year}-#{date_from.month}"
112 @periods << "#{date_from.year}-#{date_from.month}"
113 date_from = (date_from + 1.month).at_beginning_of_month
113 date_from = (date_from + 1.month).at_beginning_of_month
114 when 'week'
114 when 'week'
115 @periods << "#{date_from.year}-#{date_from.to_date.cweek}"
115 @periods << "#{date_from.year}-#{date_from.to_date.cweek}"
116 date_from = (date_from + 7.day).at_beginning_of_week
116 date_from = (date_from + 7.day).at_beginning_of_week
117 when 'day'
117 when 'day'
118 @periods << "#{date_from.to_date}"
118 @periods << "#{date_from.to_date}"
119 date_from = date_from + 1.day
119 date_from = date_from + 1.day
120 end
120 end
121 end
121 end
122 end
122 end
123
123
124 respond_to do |format|
124 respond_to do |format|
125 format.html { render :layout => !request.xhr? }
125 format.html { render :layout => !request.xhr? }
126 format.csv { send_data(report_to_csv(@criterias, @periods, @hours).read, :type => 'text/csv; header=present', :filename => 'timelog.csv') }
126 format.csv { send_data(report_to_csv(@criterias, @periods, @hours).read, :type => 'text/csv; header=present', :filename => 'timelog.csv') }
127 end
127 end
128 end
128 end
129
129
130 def details
130 def details
131 sort_init 'spent_on', 'desc'
131 sort_init 'spent_on', 'desc'
132 sort_update
132 sort_update
133
133
134 cond = ARCondition.new
134 cond = ARCondition.new
135 cond << (@issue.nil? ? @project.project_condition(Setting.display_subprojects_issues?) :
135 cond << (@issue.nil? ? @project.project_condition(Setting.display_subprojects_issues?) :
136 ["#{TimeEntry.table_name}.issue_id = ?", @issue.id])
136 ["#{TimeEntry.table_name}.issue_id = ?", @issue.id])
137
137
138 retrieve_date_range
138 retrieve_date_range
139 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
139 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
140
140
141 TimeEntry.visible_by(User.current) do
141 TimeEntry.visible_by(User.current) do
142 respond_to do |format|
142 respond_to do |format|
143 format.html {
143 format.html {
144 # Paginate results
144 # Paginate results
145 @entry_count = TimeEntry.count(:include => :project, :conditions => cond.conditions)
145 @entry_count = TimeEntry.count(:include => :project, :conditions => cond.conditions)
146 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
146 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
147 @entries = TimeEntry.find(:all,
147 @entries = TimeEntry.find(:all,
148 :include => [:project, :activity, :user, {:issue => :tracker}],
148 :include => [:project, :activity, :user, {:issue => :tracker}],
149 :conditions => cond.conditions,
149 :conditions => cond.conditions,
150 :order => sort_clause,
150 :order => sort_clause,
151 :limit => @entry_pages.items_per_page,
151 :limit => @entry_pages.items_per_page,
152 :offset => @entry_pages.current.offset)
152 :offset => @entry_pages.current.offset)
153 @total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f
153 @total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f
154
154
155 render :layout => !request.xhr?
155 render :layout => !request.xhr?
156 }
156 }
157 format.csv {
157 format.csv {
158 # Export all entries
158 # Export all entries
159 @entries = TimeEntry.find(:all,
159 @entries = TimeEntry.find(:all,
160 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
160 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
161 :conditions => cond.conditions,
161 :conditions => cond.conditions,
162 :order => sort_clause)
162 :order => sort_clause)
163 send_data(entries_to_csv(@entries).read, :type => 'text/csv; header=present', :filename => 'timelog.csv')
163 send_data(entries_to_csv(@entries).read, :type => 'text/csv; header=present', :filename => 'timelog.csv')
164 }
164 }
165 end
165 end
166 end
166 end
167 end
167 end
168
168
169 def edit
169 def edit
170 render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
170 render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
171 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
171 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
172 @time_entry.attributes = params[:time_entry]
172 @time_entry.attributes = params[:time_entry]
173 if request.post? and @time_entry.save
173 if request.post? and @time_entry.save
174 flash[:notice] = l(:notice_successful_update)
174 flash[:notice] = l(:notice_successful_update)
175 redirect_to :action => 'details', :project_id => @time_entry.project
175 redirect_to(params[:back_url] || {:action => 'details', :project_id => @time_entry.project})
176 return
176 return
177 end
177 end
178 @activities = Enumeration::get_values('ACTI')
178 @activities = Enumeration::get_values('ACTI')
179 end
179 end
180
180
181 def destroy
181 def destroy
182 render_404 and return unless @time_entry
182 render_404 and return unless @time_entry
183 render_403 and return unless @time_entry.editable_by?(User.current)
183 render_403 and return unless @time_entry.editable_by?(User.current)
184 @time_entry.destroy
184 @time_entry.destroy
185 flash[:notice] = l(:notice_successful_delete)
185 flash[:notice] = l(:notice_successful_delete)
186 redirect_to :back
186 redirect_to :back
187 rescue RedirectBackError
187 rescue RedirectBackError
188 redirect_to :action => 'details', :project_id => @time_entry.project
188 redirect_to :action => 'details', :project_id => @time_entry.project
189 end
189 end
190
190
191 private
191 private
192 def find_project
192 def find_project
193 if params[:id]
193 if params[:id]
194 @time_entry = TimeEntry.find(params[:id])
194 @time_entry = TimeEntry.find(params[:id])
195 @project = @time_entry.project
195 @project = @time_entry.project
196 elsif params[:issue_id]
196 elsif params[:issue_id]
197 @issue = Issue.find(params[:issue_id])
197 @issue = Issue.find(params[:issue_id])
198 @project = @issue.project
198 @project = @issue.project
199 elsif params[:project_id]
199 elsif params[:project_id]
200 @project = Project.find(params[:project_id])
200 @project = Project.find(params[:project_id])
201 else
201 else
202 render_404
202 render_404
203 return false
203 return false
204 end
204 end
205 rescue ActiveRecord::RecordNotFound
205 rescue ActiveRecord::RecordNotFound
206 render_404
206 render_404
207 end
207 end
208
208
209 # Retrieves the date range based on predefined ranges or specific from/to param dates
209 # Retrieves the date range based on predefined ranges or specific from/to param dates
210 def retrieve_date_range
210 def retrieve_date_range
211 @free_period = false
211 @free_period = false
212 @from, @to = nil, nil
212 @from, @to = nil, nil
213
213
214 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
214 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
215 case params[:period].to_s
215 case params[:period].to_s
216 when 'today'
216 when 'today'
217 @from = @to = Date.today
217 @from = @to = Date.today
218 when 'yesterday'
218 when 'yesterday'
219 @from = @to = Date.today - 1
219 @from = @to = Date.today - 1
220 when 'current_week'
220 when 'current_week'
221 @from = Date.today - (Date.today.cwday - 1)%7
221 @from = Date.today - (Date.today.cwday - 1)%7
222 @to = @from + 6
222 @to = @from + 6
223 when 'last_week'
223 when 'last_week'
224 @from = Date.today - 7 - (Date.today.cwday - 1)%7
224 @from = Date.today - 7 - (Date.today.cwday - 1)%7
225 @to = @from + 6
225 @to = @from + 6
226 when '7_days'
226 when '7_days'
227 @from = Date.today - 7
227 @from = Date.today - 7
228 @to = Date.today
228 @to = Date.today
229 when 'current_month'
229 when 'current_month'
230 @from = Date.civil(Date.today.year, Date.today.month, 1)
230 @from = Date.civil(Date.today.year, Date.today.month, 1)
231 @to = (@from >> 1) - 1
231 @to = (@from >> 1) - 1
232 when 'last_month'
232 when 'last_month'
233 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
233 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
234 @to = (@from >> 1) - 1
234 @to = (@from >> 1) - 1
235 when '30_days'
235 when '30_days'
236 @from = Date.today - 30
236 @from = Date.today - 30
237 @to = Date.today
237 @to = Date.today
238 when 'current_year'
238 when 'current_year'
239 @from = Date.civil(Date.today.year, 1, 1)
239 @from = Date.civil(Date.today.year, 1, 1)
240 @to = Date.civil(Date.today.year, 12, 31)
240 @to = Date.civil(Date.today.year, 12, 31)
241 end
241 end
242 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
242 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
243 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
243 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
244 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
244 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
245 @free_period = true
245 @free_period = true
246 else
246 else
247 # default
247 # default
248 end
248 end
249
249
250 @from, @to = @to, @from if @from && @to && @from > @to
250 @from, @to = @to, @from if @from && @to && @from > @to
251 @from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) - 1
251 @from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) - 1
252 @to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today)
252 @to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today)
253 end
253 end
254 end
254 end
@@ -1,506 +1,510
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module ApplicationHelper
18 module ApplicationHelper
19 include Redmine::WikiFormatting::Macros::Definitions
19 include Redmine::WikiFormatting::Macros::Definitions
20
20
21 def current_role
21 def current_role
22 @current_role ||= User.current.role_for_project(@project)
22 @current_role ||= User.current.role_for_project(@project)
23 end
23 end
24
24
25 # Return true if user is authorized for controller/action, otherwise false
25 # Return true if user is authorized for controller/action, otherwise false
26 def authorize_for(controller, action)
26 def authorize_for(controller, action)
27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
28 end
28 end
29
29
30 # Display a link if user is authorized
30 # Display a link if user is authorized
31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
33 end
33 end
34
34
35 # Display a link to user's account page
35 # Display a link to user's account page
36 def link_to_user(user)
36 def link_to_user(user)
37 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
37 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
38 end
38 end
39
39
40 def link_to_issue(issue, options={})
40 def link_to_issue(issue, options={})
41 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
41 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
42 end
42 end
43
43
44 def toggle_link(name, id, options={})
44 def toggle_link(name, id, options={})
45 onclick = "Element.toggle('#{id}'); "
45 onclick = "Element.toggle('#{id}'); "
46 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
46 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
47 onclick << "return false;"
47 onclick << "return false;"
48 link_to(name, "#", :onclick => onclick)
48 link_to(name, "#", :onclick => onclick)
49 end
49 end
50
50
51 def show_and_goto_link(name, id, options={})
51 def show_and_goto_link(name, id, options={})
52 onclick = "Element.show('#{id}'); "
52 onclick = "Element.show('#{id}'); "
53 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
53 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
54 onclick << "Element.scrollTo('#{id}'); "
54 onclick << "Element.scrollTo('#{id}'); "
55 onclick << "return false;"
55 onclick << "return false;"
56 link_to(name, "#", options.merge(:onclick => onclick))
56 link_to(name, "#", options.merge(:onclick => onclick))
57 end
57 end
58
58
59 def image_to_function(name, function, html_options = {})
59 def image_to_function(name, function, html_options = {})
60 html_options.symbolize_keys!
60 html_options.symbolize_keys!
61 tag(:input, html_options.merge({
61 tag(:input, html_options.merge({
62 :type => "image", :src => image_path(name),
62 :type => "image", :src => image_path(name),
63 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
63 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
64 }))
64 }))
65 end
65 end
66
66
67 def prompt_to_remote(name, text, param, url, html_options = {})
67 def prompt_to_remote(name, text, param, url, html_options = {})
68 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
68 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
69 link_to name, {}, html_options
69 link_to name, {}, html_options
70 end
70 end
71
71
72 def format_date(date)
72 def format_date(date)
73 return nil unless date
73 return nil unless date
74 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
74 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
75 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
75 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
76 date.strftime(@date_format)
76 date.strftime(@date_format)
77 end
77 end
78
78
79 def format_time(time, include_date = true)
79 def format_time(time, include_date = true)
80 return nil unless time
80 return nil unless time
81 time = time.to_time if time.is_a?(String)
81 time = time.to_time if time.is_a?(String)
82 zone = User.current.time_zone
82 zone = User.current.time_zone
83 if time.utc?
83 if time.utc?
84 local = zone ? zone.adjust(time) : time.getlocal
84 local = zone ? zone.adjust(time) : time.getlocal
85 else
85 else
86 local = zone ? zone.adjust(time.getutc) : time
86 local = zone ? zone.adjust(time.getutc) : time
87 end
87 end
88 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
88 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
89 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
89 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
90 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
90 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
91 end
91 end
92
92
93 def html_hours(text)
93 def html_hours(text)
94 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
94 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
95 end
95 end
96
96
97 def authoring(created, author)
97 def authoring(created, author)
98 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
98 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
99 l(:label_added_time_by, author || 'Anonymous', time_tag)
99 l(:label_added_time_by, author || 'Anonymous', time_tag)
100 end
100 end
101
101
102 def l_or_humanize(s)
102 def l_or_humanize(s)
103 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
103 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
104 end
104 end
105
105
106 def day_name(day)
106 def day_name(day)
107 l(:general_day_names).split(',')[day-1]
107 l(:general_day_names).split(',')[day-1]
108 end
108 end
109
109
110 def month_name(month)
110 def month_name(month)
111 l(:actionview_datehelper_select_month_names).split(',')[month-1]
111 l(:actionview_datehelper_select_month_names).split(',')[month-1]
112 end
112 end
113
113
114 def pagination_links_full(paginator, count=nil, options={})
114 def pagination_links_full(paginator, count=nil, options={})
115 page_param = options.delete(:page_param) || :page
115 page_param = options.delete(:page_param) || :page
116 url_param = params.dup
116 url_param = params.dup
117 # don't reuse params if filters are present
117 # don't reuse params if filters are present
118 url_param.clear if url_param.has_key?(:set_filter)
118 url_param.clear if url_param.has_key?(:set_filter)
119
119
120 html = ''
120 html = ''
121 html << link_to_remote(('&#171; ' + l(:label_previous)),
121 html << link_to_remote(('&#171; ' + l(:label_previous)),
122 {:update => 'content',
122 {:update => 'content',
123 :url => url_param.merge(page_param => paginator.current.previous),
123 :url => url_param.merge(page_param => paginator.current.previous),
124 :complete => 'window.scrollTo(0,0)'},
124 :complete => 'window.scrollTo(0,0)'},
125 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
125 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
126
126
127 html << (pagination_links_each(paginator, options) do |n|
127 html << (pagination_links_each(paginator, options) do |n|
128 link_to_remote(n.to_s,
128 link_to_remote(n.to_s,
129 {:url => {:params => url_param.merge(page_param => n)},
129 {:url => {:params => url_param.merge(page_param => n)},
130 :update => 'content',
130 :update => 'content',
131 :complete => 'window.scrollTo(0,0)'},
131 :complete => 'window.scrollTo(0,0)'},
132 {:href => url_for(:params => url_param.merge(page_param => n))})
132 {:href => url_for(:params => url_param.merge(page_param => n))})
133 end || '')
133 end || '')
134
134
135 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
135 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
136 {:update => 'content',
136 {:update => 'content',
137 :url => url_param.merge(page_param => paginator.current.next),
137 :url => url_param.merge(page_param => paginator.current.next),
138 :complete => 'window.scrollTo(0,0)'},
138 :complete => 'window.scrollTo(0,0)'},
139 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
139 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
140
140
141 unless count.nil?
141 unless count.nil?
142 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
142 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
143 end
143 end
144
144
145 html
145 html
146 end
146 end
147
147
148 def per_page_links(selected=nil)
148 def per_page_links(selected=nil)
149 url_param = params.dup
149 url_param = params.dup
150 url_param.clear if url_param.has_key?(:set_filter)
150 url_param.clear if url_param.has_key?(:set_filter)
151
151
152 links = Setting.per_page_options_array.collect do |n|
152 links = Setting.per_page_options_array.collect do |n|
153 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
153 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
154 {:href => url_for(url_param.merge(:per_page => n))})
154 {:href => url_for(url_param.merge(:per_page => n))})
155 end
155 end
156 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
156 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
157 end
157 end
158
158
159 def breadcrumb(*args)
159 def breadcrumb(*args)
160 content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb')
160 content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb')
161 end
161 end
162
162
163 def html_title(*args)
163 def html_title(*args)
164 if args.empty?
164 if args.empty?
165 title = []
165 title = []
166 title << @project.name if @project
166 title << @project.name if @project
167 title += @html_title if @html_title
167 title += @html_title if @html_title
168 title << Setting.app_title
168 title << Setting.app_title
169 title.compact.join(' - ')
169 title.compact.join(' - ')
170 else
170 else
171 @html_title ||= []
171 @html_title ||= []
172 @html_title += args
172 @html_title += args
173 end
173 end
174 end
174 end
175
175
176 def accesskey(s)
176 def accesskey(s)
177 Redmine::AccessKeys.key_for s
177 Redmine::AccessKeys.key_for s
178 end
178 end
179
179
180 # Formats text according to system settings.
180 # Formats text according to system settings.
181 # 2 ways to call this method:
181 # 2 ways to call this method:
182 # * with a String: textilizable(text, options)
182 # * with a String: textilizable(text, options)
183 # * with an object and one of its attribute: textilizable(issue, :description, options)
183 # * with an object and one of its attribute: textilizable(issue, :description, options)
184 def textilizable(*args)
184 def textilizable(*args)
185 options = args.last.is_a?(Hash) ? args.pop : {}
185 options = args.last.is_a?(Hash) ? args.pop : {}
186 case args.size
186 case args.size
187 when 1
187 when 1
188 obj = nil
188 obj = nil
189 text = args.shift
189 text = args.shift
190 when 2
190 when 2
191 obj = args.shift
191 obj = args.shift
192 text = obj.send(args.shift).to_s
192 text = obj.send(args.shift).to_s
193 else
193 else
194 raise ArgumentError, 'invalid arguments to textilizable'
194 raise ArgumentError, 'invalid arguments to textilizable'
195 end
195 end
196 return '' if text.blank?
196 return '' if text.blank?
197
197
198 only_path = options.delete(:only_path) == false ? false : true
198 only_path = options.delete(:only_path) == false ? false : true
199
199
200 # when using an image link, try to use an attachment, if possible
200 # when using an image link, try to use an attachment, if possible
201 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
201 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
202
202
203 if attachments
203 if attachments
204 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
204 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
205 style = $1
205 style = $1
206 filename = $6
206 filename = $6
207 rf = Regexp.new(filename, Regexp::IGNORECASE)
207 rf = Regexp.new(filename, Regexp::IGNORECASE)
208 # search for the picture in attachments
208 # search for the picture in attachments
209 if found = attachments.detect { |att| att.filename =~ rf }
209 if found = attachments.detect { |att| att.filename =~ rf }
210 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
210 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
211 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
211 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
212 alt = desc.blank? ? nil : "(#{desc})"
212 alt = desc.blank? ? nil : "(#{desc})"
213 "!#{style}#{image_url}#{alt}!"
213 "!#{style}#{image_url}#{alt}!"
214 else
214 else
215 "!#{style}#{filename}!"
215 "!#{style}#{filename}!"
216 end
216 end
217 end
217 end
218 end
218 end
219
219
220 text = (Setting.text_formatting == 'textile') ?
220 text = (Setting.text_formatting == 'textile') ?
221 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
221 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
222 simple_format(auto_link(h(text)))
222 simple_format(auto_link(h(text)))
223
223
224 # different methods for formatting wiki links
224 # different methods for formatting wiki links
225 case options[:wiki_links]
225 case options[:wiki_links]
226 when :local
226 when :local
227 # used for local links to html files
227 # used for local links to html files
228 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
228 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
229 when :anchor
229 when :anchor
230 # used for single-file wiki export
230 # used for single-file wiki export
231 format_wiki_link = Proc.new {|project, title| "##{title}" }
231 format_wiki_link = Proc.new {|project, title| "##{title}" }
232 else
232 else
233 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
233 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
234 end
234 end
235
235
236 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
236 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
237
237
238 # Wiki links
238 # Wiki links
239 #
239 #
240 # Examples:
240 # Examples:
241 # [[mypage]]
241 # [[mypage]]
242 # [[mypage|mytext]]
242 # [[mypage|mytext]]
243 # wiki links can refer other project wikis, using project name or identifier:
243 # wiki links can refer other project wikis, using project name or identifier:
244 # [[project:]] -> wiki starting page
244 # [[project:]] -> wiki starting page
245 # [[project:|mytext]]
245 # [[project:|mytext]]
246 # [[project:mypage]]
246 # [[project:mypage]]
247 # [[project:mypage|mytext]]
247 # [[project:mypage|mytext]]
248 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
248 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
249 link_project = project
249 link_project = project
250 esc, all, page, title = $1, $2, $3, $5
250 esc, all, page, title = $1, $2, $3, $5
251 if esc.nil?
251 if esc.nil?
252 if page =~ /^([^\:]+)\:(.*)$/
252 if page =~ /^([^\:]+)\:(.*)$/
253 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
253 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
254 page = $2
254 page = $2
255 title ||= $1 if page.blank?
255 title ||= $1 if page.blank?
256 end
256 end
257
257
258 if link_project && link_project.wiki
258 if link_project && link_project.wiki
259 # check if page exists
259 # check if page exists
260 wiki_page = link_project.wiki.find_page(page)
260 wiki_page = link_project.wiki.find_page(page)
261 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
261 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
262 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
262 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
263 else
263 else
264 # project or wiki doesn't exist
264 # project or wiki doesn't exist
265 title || page
265 title || page
266 end
266 end
267 else
267 else
268 all
268 all
269 end
269 end
270 end
270 end
271
271
272 # Redmine links
272 # Redmine links
273 #
273 #
274 # Examples:
274 # Examples:
275 # Issues:
275 # Issues:
276 # #52 -> Link to issue #52
276 # #52 -> Link to issue #52
277 # Changesets:
277 # Changesets:
278 # r52 -> Link to revision 52
278 # r52 -> Link to revision 52
279 # commit:a85130f -> Link to scmid starting with a85130f
279 # commit:a85130f -> Link to scmid starting with a85130f
280 # Documents:
280 # Documents:
281 # document#17 -> Link to document with id 17
281 # document#17 -> Link to document with id 17
282 # document:Greetings -> Link to the document with title "Greetings"
282 # document:Greetings -> Link to the document with title "Greetings"
283 # document:"Some document" -> Link to the document with title "Some document"
283 # document:"Some document" -> Link to the document with title "Some document"
284 # Versions:
284 # Versions:
285 # version#3 -> Link to version with id 3
285 # version#3 -> Link to version with id 3
286 # version:1.0.0 -> Link to version named "1.0.0"
286 # version:1.0.0 -> Link to version named "1.0.0"
287 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
287 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
288 # Attachments:
288 # Attachments:
289 # attachment:file.zip -> Link to the attachment of the current object named file.zip
289 # attachment:file.zip -> Link to the attachment of the current object named file.zip
290 # Source files:
290 # Source files:
291 # source:some/file -> Link to the file located at /some/file in the project's repository
291 # source:some/file -> Link to the file located at /some/file in the project's repository
292 # source:some/file@52 -> Link to the file's revision 52
292 # source:some/file@52 -> Link to the file's revision 52
293 # source:some/file#L120 -> Link to line 120 of the file
293 # source:some/file#L120 -> Link to line 120 of the file
294 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
294 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
295 # export:some/file -> Force the download of the file
295 # export:some/file -> Force the download of the file
296 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
296 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
297 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
297 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
298 link = nil
298 link = nil
299 if esc.nil?
299 if esc.nil?
300 if prefix.nil? && sep == 'r'
300 if prefix.nil? && sep == 'r'
301 if project && (changeset = project.changesets.find_by_revision(oid))
301 if project && (changeset = project.changesets.find_by_revision(oid))
302 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
302 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
303 :class => 'changeset',
303 :class => 'changeset',
304 :title => truncate(changeset.comments, 100))
304 :title => truncate(changeset.comments, 100))
305 end
305 end
306 elsif sep == '#'
306 elsif sep == '#'
307 oid = oid.to_i
307 oid = oid.to_i
308 case prefix
308 case prefix
309 when nil
309 when nil
310 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
310 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
311 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
311 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
312 :class => (issue.closed? ? 'issue closed' : 'issue'),
312 :class => (issue.closed? ? 'issue closed' : 'issue'),
313 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
313 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
314 link = content_tag('del', link) if issue.closed?
314 link = content_tag('del', link) if issue.closed?
315 end
315 end
316 when 'document'
316 when 'document'
317 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
317 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
318 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
318 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
319 :class => 'document'
319 :class => 'document'
320 end
320 end
321 when 'version'
321 when 'version'
322 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
322 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
323 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
323 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
324 :class => 'version'
324 :class => 'version'
325 end
325 end
326 end
326 end
327 elsif sep == ':'
327 elsif sep == ':'
328 # removes the double quotes if any
328 # removes the double quotes if any
329 name = oid.gsub(%r{^"(.*)"$}, "\\1")
329 name = oid.gsub(%r{^"(.*)"$}, "\\1")
330 case prefix
330 case prefix
331 when 'document'
331 when 'document'
332 if project && document = project.documents.find_by_title(name)
332 if project && document = project.documents.find_by_title(name)
333 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
333 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
334 :class => 'document'
334 :class => 'document'
335 end
335 end
336 when 'version'
336 when 'version'
337 if project && version = project.versions.find_by_name(name)
337 if project && version = project.versions.find_by_name(name)
338 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
338 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
339 :class => 'version'
339 :class => 'version'
340 end
340 end
341 when 'commit'
341 when 'commit'
342 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
342 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
343 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, :class => 'changeset', :title => truncate(changeset.comments, 100)
343 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, :class => 'changeset', :title => truncate(changeset.comments, 100)
344 end
344 end
345 when 'source', 'export'
345 when 'source', 'export'
346 if project && project.repository
346 if project && project.repository
347 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
347 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
348 path, rev, anchor = $1, $3, $5
348 path, rev, anchor = $1, $3, $5
349 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path,
349 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path,
350 :rev => rev,
350 :rev => rev,
351 :anchor => anchor,
351 :anchor => anchor,
352 :format => (prefix == 'export' ? 'raw' : nil)},
352 :format => (prefix == 'export' ? 'raw' : nil)},
353 :class => (prefix == 'export' ? 'source download' : 'source')
353 :class => (prefix == 'export' ? 'source download' : 'source')
354 end
354 end
355 when 'attachment'
355 when 'attachment'
356 if attachments && attachment = attachments.detect {|a| a.filename == name }
356 if attachments && attachment = attachments.detect {|a| a.filename == name }
357 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
357 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
358 :class => 'attachment'
358 :class => 'attachment'
359 end
359 end
360 end
360 end
361 end
361 end
362 end
362 end
363 leading + (link || "#{prefix}#{sep}#{oid}")
363 leading + (link || "#{prefix}#{sep}#{oid}")
364 end
364 end
365
365
366 text
366 text
367 end
367 end
368
368
369 # Same as Rails' simple_format helper without using paragraphs
369 # Same as Rails' simple_format helper without using paragraphs
370 def simple_format_without_paragraph(text)
370 def simple_format_without_paragraph(text)
371 text.to_s.
371 text.to_s.
372 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
372 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
373 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
373 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
374 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
374 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
375 end
375 end
376
376
377 def error_messages_for(object_name, options = {})
377 def error_messages_for(object_name, options = {})
378 options = options.symbolize_keys
378 options = options.symbolize_keys
379 object = instance_variable_get("@#{object_name}")
379 object = instance_variable_get("@#{object_name}")
380 if object && !object.errors.empty?
380 if object && !object.errors.empty?
381 # build full_messages here with controller current language
381 # build full_messages here with controller current language
382 full_messages = []
382 full_messages = []
383 object.errors.each do |attr, msg|
383 object.errors.each do |attr, msg|
384 next if msg.nil?
384 next if msg.nil?
385 msg = msg.first if msg.is_a? Array
385 msg = msg.first if msg.is_a? Array
386 if attr == "base"
386 if attr == "base"
387 full_messages << l(msg)
387 full_messages << l(msg)
388 else
388 else
389 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
389 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
390 end
390 end
391 end
391 end
392 # retrieve custom values error messages
392 # retrieve custom values error messages
393 if object.errors[:custom_values]
393 if object.errors[:custom_values]
394 object.custom_values.each do |v|
394 object.custom_values.each do |v|
395 v.errors.each do |attr, msg|
395 v.errors.each do |attr, msg|
396 next if msg.nil?
396 next if msg.nil?
397 msg = msg.first if msg.is_a? Array
397 msg = msg.first if msg.is_a? Array
398 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
398 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
399 end
399 end
400 end
400 end
401 end
401 end
402 content_tag("div",
402 content_tag("div",
403 content_tag(
403 content_tag(
404 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
404 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
405 ) +
405 ) +
406 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
406 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
407 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
407 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
408 )
408 )
409 else
409 else
410 ""
410 ""
411 end
411 end
412 end
412 end
413
413
414 def lang_options_for_select(blank=true)
414 def lang_options_for_select(blank=true)
415 (blank ? [["(auto)", ""]] : []) +
415 (blank ? [["(auto)", ""]] : []) +
416 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
416 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
417 end
417 end
418
418
419 def label_tag_for(name, option_tags = nil, options = {})
419 def label_tag_for(name, option_tags = nil, options = {})
420 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
420 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
421 content_tag("label", label_text)
421 content_tag("label", label_text)
422 end
422 end
423
423
424 def labelled_tabular_form_for(name, object, options, &proc)
424 def labelled_tabular_form_for(name, object, options, &proc)
425 options[:html] ||= {}
425 options[:html] ||= {}
426 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
426 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
427 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
427 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
428 end
428 end
429
429
430 def back_url_hidden_field_tag
431 hidden_field_tag 'back_url', (params[:back_url] || request.env['HTTP_REFERER'])
432 end
433
430 def check_all_links(form_name)
434 def check_all_links(form_name)
431 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
435 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
432 " | " +
436 " | " +
433 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
437 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
434 end
438 end
435
439
436 def progress_bar(pcts, options={})
440 def progress_bar(pcts, options={})
437 pcts = [pcts, pcts] unless pcts.is_a?(Array)
441 pcts = [pcts, pcts] unless pcts.is_a?(Array)
438 pcts[1] = pcts[1] - pcts[0]
442 pcts[1] = pcts[1] - pcts[0]
439 pcts << (100 - pcts[1] - pcts[0])
443 pcts << (100 - pcts[1] - pcts[0])
440 width = options[:width] || '100px;'
444 width = options[:width] || '100px;'
441 legend = options[:legend] || ''
445 legend = options[:legend] || ''
442 content_tag('table',
446 content_tag('table',
443 content_tag('tr',
447 content_tag('tr',
444 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
448 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
445 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
449 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
446 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
450 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
447 ), :class => 'progress', :style => "width: #{width};") +
451 ), :class => 'progress', :style => "width: #{width};") +
448 content_tag('p', legend, :class => 'pourcent')
452 content_tag('p', legend, :class => 'pourcent')
449 end
453 end
450
454
451 def context_menu_link(name, url, options={})
455 def context_menu_link(name, url, options={})
452 options[:class] ||= ''
456 options[:class] ||= ''
453 if options.delete(:selected)
457 if options.delete(:selected)
454 options[:class] << ' icon-checked disabled'
458 options[:class] << ' icon-checked disabled'
455 options[:disabled] = true
459 options[:disabled] = true
456 end
460 end
457 if options.delete(:disabled)
461 if options.delete(:disabled)
458 options.delete(:method)
462 options.delete(:method)
459 options.delete(:confirm)
463 options.delete(:confirm)
460 options.delete(:onclick)
464 options.delete(:onclick)
461 options[:class] << ' disabled'
465 options[:class] << ' disabled'
462 url = '#'
466 url = '#'
463 end
467 end
464 link_to name, url, options
468 link_to name, url, options
465 end
469 end
466
470
467 def calendar_for(field_id)
471 def calendar_for(field_id)
468 include_calendar_headers_tags
472 include_calendar_headers_tags
469 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
473 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
470 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
474 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
471 end
475 end
472
476
473 def include_calendar_headers_tags
477 def include_calendar_headers_tags
474 unless @calendar_headers_tags_included
478 unless @calendar_headers_tags_included
475 @calendar_headers_tags_included = true
479 @calendar_headers_tags_included = true
476 content_for :header_tags do
480 content_for :header_tags do
477 javascript_include_tag('calendar/calendar') +
481 javascript_include_tag('calendar/calendar') +
478 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
482 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
479 javascript_include_tag('calendar/calendar-setup') +
483 javascript_include_tag('calendar/calendar-setup') +
480 stylesheet_link_tag('calendar')
484 stylesheet_link_tag('calendar')
481 end
485 end
482 end
486 end
483 end
487 end
484
488
485 def wikitoolbar_for(field_id)
489 def wikitoolbar_for(field_id)
486 return '' unless Setting.text_formatting == 'textile'
490 return '' unless Setting.text_formatting == 'textile'
487
491
488 help_link = l(:setting_text_formatting) + ': ' +
492 help_link = l(:setting_text_formatting) + ': ' +
489 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
493 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
490 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
494 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
491
495
492 javascript_include_tag('jstoolbar/jstoolbar') +
496 javascript_include_tag('jstoolbar/jstoolbar') +
493 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
497 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
494 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
498 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
495 end
499 end
496
500
497 def content_for(name, content = nil, &block)
501 def content_for(name, content = nil, &block)
498 @has_content ||= {}
502 @has_content ||= {}
499 @has_content[name] = true
503 @has_content[name] = true
500 super(name, content, &block)
504 super(name, content, &block)
501 end
505 end
502
506
503 def has_content?(name)
507 def has_content?(name)
504 (@has_content && @has_content[name]) || false
508 (@has_content && @has_content[name]) || false
505 end
509 end
506 end
510 end
@@ -1,16 +1,17
1 <h2><%= l(:label_spent_time) %></h2>
1 <h2><%= l(:label_spent_time) %></h2>
2
2
3 <% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :project_id => @time_entry.project} do |f| %>
3 <% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :project_id => @time_entry.project} do |f| %>
4 <%= error_messages_for 'time_entry' %>
4 <%= error_messages_for 'time_entry' %>
5 <%= back_url_hidden_field_tag %>
5
6
6 <div class="box">
7 <div class="box">
7 <p><%= f.text_field :issue_id, :size => 6 %> <em><%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %></em></p>
8 <p><%= f.text_field :issue_id, :size => 6 %> <em><%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %></em></p>
8 <p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>
9 <p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>
9 <p><%= f.text_field :hours, :size => 6, :required => true %></p>
10 <p><%= f.text_field :hours, :size => 6, :required => true %></p>
10 <p><%= f.text_field :comments, :size => 100 %></p>
11 <p><%= f.text_field :comments, :size => 100 %></p>
11 <p><%= f.select :activity_id, (@activities.collect {|p| [p.name, p.id]}), :required => true %></p>
12 <p><%= f.select :activity_id, (@activities.collect {|p| [p.name, p.id]}), :required => true %></p>
12 </div>
13 </div>
13
14
14 <%= submit_tag l(:button_save) %>
15 <%= submit_tag l(:button_save) %>
15
16
16 <% end %>
17 <% end %>
General Comments 0
You need to be logged in to leave comments. Login now