##// END OF EJS Templates
Add Redmine links for repository files using source: and export: keyworkds (#867)....
Jean-Philippe Lang -
r1252:93d1b2e0a439
parent child
Show More
@@ -1,298 +1,299
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 require 'SVG/Graph/Bar'
18 require 'SVG/Graph/Bar'
19 require 'SVG/Graph/BarHorizontal'
19 require 'SVG/Graph/BarHorizontal'
20 require 'digest/sha1'
20 require 'digest/sha1'
21
21
22 class ChangesetNotFound < Exception
22 class ChangesetNotFound < Exception
23 end
23 end
24
24
25 class RepositoriesController < ApplicationController
25 class RepositoriesController < ApplicationController
26 layout 'base'
26 layout 'base'
27 menu_item :repository
27 menu_item :repository
28 before_filter :find_repository, :except => :edit
28 before_filter :find_repository, :except => :edit
29 before_filter :find_project, :only => :edit
29 before_filter :find_project, :only => :edit
30 before_filter :authorize
30 before_filter :authorize
31 accept_key_auth :revisions
31 accept_key_auth :revisions
32
32
33 def edit
33 def edit
34 @repository = @project.repository
34 @repository = @project.repository
35 if !@repository
35 if !@repository
36 @repository = Repository.factory(params[:repository_scm])
36 @repository = Repository.factory(params[:repository_scm])
37 @repository.project = @project
37 @repository.project = @project
38 end
38 end
39 if request.post?
39 if request.post?
40 @repository.attributes = params[:repository]
40 @repository.attributes = params[:repository]
41 @repository.save
41 @repository.save
42 end
42 end
43 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
43 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
44 end
44 end
45
45
46 def destroy
46 def destroy
47 @repository.destroy
47 @repository.destroy
48 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
48 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
49 end
49 end
50
50
51 def show
51 def show
52 # check if new revisions have been committed in the repository
52 # check if new revisions have been committed in the repository
53 @repository.fetch_changesets if Setting.autofetch_changesets?
53 @repository.fetch_changesets if Setting.autofetch_changesets?
54 # get entries for the browse frame
54 # get entries for the browse frame
55 @entries = @repository.entries('')
55 @entries = @repository.entries('')
56 # latest changesets
56 # latest changesets
57 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
57 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
58 show_error_not_found unless @entries || @changesets.any?
58 show_error_not_found unless @entries || @changesets.any?
59 rescue Redmine::Scm::Adapters::CommandFailed => e
59 rescue Redmine::Scm::Adapters::CommandFailed => e
60 show_error_command_failed(e.message)
60 show_error_command_failed(e.message)
61 end
61 end
62
62
63 def browse
63 def browse
64 @entries = @repository.entries(@path, @rev)
64 @entries = @repository.entries(@path, @rev)
65 if request.xhr?
65 if request.xhr?
66 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
66 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
67 else
67 else
68 show_error_not_found unless @entries
68 show_error_not_found unless @entries
69 end
69 end
70 rescue Redmine::Scm::Adapters::CommandFailed => e
70 rescue Redmine::Scm::Adapters::CommandFailed => e
71 show_error_command_failed(e.message)
71 show_error_command_failed(e.message)
72 end
72 end
73
73
74 def changes
74 def changes
75 @entry = @repository.scm.entry(@path, @rev)
75 @entry = @repository.scm.entry(@path, @rev)
76 show_error_not_found and return unless @entry
76 show_error_not_found and return unless @entry
77 @changesets = @repository.changesets_for_path(@path)
77 @changesets = @repository.changesets_for_path(@path)
78 rescue Redmine::Scm::Adapters::CommandFailed => e
78 rescue Redmine::Scm::Adapters::CommandFailed => e
79 show_error_command_failed(e.message)
79 show_error_command_failed(e.message)
80 end
80 end
81
81
82 def revisions
82 def revisions
83 @changeset_count = @repository.changesets.count
83 @changeset_count = @repository.changesets.count
84 @changeset_pages = Paginator.new self, @changeset_count,
84 @changeset_pages = Paginator.new self, @changeset_count,
85 per_page_option,
85 per_page_option,
86 params['page']
86 params['page']
87 @changesets = @repository.changesets.find(:all,
87 @changesets = @repository.changesets.find(:all,
88 :limit => @changeset_pages.items_per_page,
88 :limit => @changeset_pages.items_per_page,
89 :offset => @changeset_pages.current.offset)
89 :offset => @changeset_pages.current.offset)
90
90
91 respond_to do |format|
91 respond_to do |format|
92 format.html { render :layout => false if request.xhr? }
92 format.html { render :layout => false if request.xhr? }
93 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
93 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
94 end
94 end
95 end
95 end
96
96
97 def entry
97 def entry
98 @content = @repository.scm.cat(@path, @rev)
98 @content = @repository.scm.cat(@path, @rev)
99 show_error_not_found and return unless @content
99 show_error_not_found and return unless @content
100 if 'raw' == params[:format]
100 if 'raw' == params[:format] || @content.is_binary_data?
101 # Force the download if it's a binary file
101 send_data @content, :filename => @path.split('/').last
102 send_data @content, :filename => @path.split('/').last
102 else
103 else
103 # Prevent empty lines when displaying a file with Windows style eol
104 # Prevent empty lines when displaying a file with Windows style eol
104 @content.gsub!("\r\n", "\n")
105 @content.gsub!("\r\n", "\n")
105 end
106 end
106 rescue Redmine::Scm::Adapters::CommandFailed => e
107 rescue Redmine::Scm::Adapters::CommandFailed => e
107 show_error_command_failed(e.message)
108 show_error_command_failed(e.message)
108 end
109 end
109
110
110 def annotate
111 def annotate
111 @annotate = @repository.scm.annotate(@path, @rev)
112 @annotate = @repository.scm.annotate(@path, @rev)
112 show_error_not_found and return if @annotate.nil? || @annotate.empty?
113 show_error_not_found and return if @annotate.nil? || @annotate.empty?
113 rescue Redmine::Scm::Adapters::CommandFailed => e
114 rescue Redmine::Scm::Adapters::CommandFailed => e
114 show_error_command_failed(e.message)
115 show_error_command_failed(e.message)
115 end
116 end
116
117
117 def revision
118 def revision
118 @changeset = @repository.changesets.find_by_revision(@rev)
119 @changeset = @repository.changesets.find_by_revision(@rev)
119 raise ChangesetNotFound unless @changeset
120 raise ChangesetNotFound unless @changeset
120 @changes_count = @changeset.changes.size
121 @changes_count = @changeset.changes.size
121 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
122 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
122 @changes = @changeset.changes.find(:all,
123 @changes = @changeset.changes.find(:all,
123 :limit => @changes_pages.items_per_page,
124 :limit => @changes_pages.items_per_page,
124 :offset => @changes_pages.current.offset)
125 :offset => @changes_pages.current.offset)
125
126
126 respond_to do |format|
127 respond_to do |format|
127 format.html
128 format.html
128 format.js {render :layout => false}
129 format.js {render :layout => false}
129 end
130 end
130 rescue ChangesetNotFound
131 rescue ChangesetNotFound
131 show_error_not_found
132 show_error_not_found
132 rescue Redmine::Scm::Adapters::CommandFailed => e
133 rescue Redmine::Scm::Adapters::CommandFailed => e
133 show_error_command_failed(e.message)
134 show_error_command_failed(e.message)
134 end
135 end
135
136
136 def diff
137 def diff
137 @rev_to = params[:rev_to]
138 @rev_to = params[:rev_to]
138 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
139 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
139 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
140 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
140
141
141 # Save diff type as user preference
142 # Save diff type as user preference
142 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
143 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
143 User.current.pref[:diff_type] = @diff_type
144 User.current.pref[:diff_type] = @diff_type
144 User.current.preference.save
145 User.current.preference.save
145 end
146 end
146
147
147 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
148 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
148 unless read_fragment(@cache_key)
149 unless read_fragment(@cache_key)
149 @diff = @repository.diff(@path, @rev, @rev_to, @diff_type)
150 @diff = @repository.diff(@path, @rev, @rev_to, @diff_type)
150 show_error_not_found unless @diff
151 show_error_not_found unless @diff
151 end
152 end
152 rescue Redmine::Scm::Adapters::CommandFailed => e
153 rescue Redmine::Scm::Adapters::CommandFailed => e
153 show_error_command_failed(e.message)
154 show_error_command_failed(e.message)
154 end
155 end
155
156
156 def stats
157 def stats
157 end
158 end
158
159
159 def graph
160 def graph
160 data = nil
161 data = nil
161 case params[:graph]
162 case params[:graph]
162 when "commits_per_month"
163 when "commits_per_month"
163 data = graph_commits_per_month(@repository)
164 data = graph_commits_per_month(@repository)
164 when "commits_per_author"
165 when "commits_per_author"
165 data = graph_commits_per_author(@repository)
166 data = graph_commits_per_author(@repository)
166 end
167 end
167 if data
168 if data
168 headers["Content-Type"] = "image/svg+xml"
169 headers["Content-Type"] = "image/svg+xml"
169 send_data(data, :type => "image/svg+xml", :disposition => "inline")
170 send_data(data, :type => "image/svg+xml", :disposition => "inline")
170 else
171 else
171 render_404
172 render_404
172 end
173 end
173 end
174 end
174
175
175 private
176 private
176 def find_project
177 def find_project
177 @project = Project.find(params[:id])
178 @project = Project.find(params[:id])
178 rescue ActiveRecord::RecordNotFound
179 rescue ActiveRecord::RecordNotFound
179 render_404
180 render_404
180 end
181 end
181
182
182 def find_repository
183 def find_repository
183 @project = Project.find(params[:id])
184 @project = Project.find(params[:id])
184 @repository = @project.repository
185 @repository = @project.repository
185 render_404 and return false unless @repository
186 render_404 and return false unless @repository
186 @path = params[:path].join('/') unless params[:path].nil?
187 @path = params[:path].join('/') unless params[:path].nil?
187 @path ||= ''
188 @path ||= ''
188 @rev = params[:rev]
189 @rev = params[:rev]
189 rescue ActiveRecord::RecordNotFound
190 rescue ActiveRecord::RecordNotFound
190 render_404
191 render_404
191 end
192 end
192
193
193 def show_error_not_found
194 def show_error_not_found
194 render_error l(:error_scm_not_found)
195 render_error l(:error_scm_not_found)
195 end
196 end
196
197
197 def show_error_command_failed(msg)
198 def show_error_command_failed(msg)
198 render_error l(:error_scm_command_failed, msg)
199 render_error l(:error_scm_command_failed, msg)
199 end
200 end
200
201
201 def graph_commits_per_month(repository)
202 def graph_commits_per_month(repository)
202 @date_to = Date.today
203 @date_to = Date.today
203 @date_from = @date_to << 11
204 @date_from = @date_to << 11
204 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
205 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
205 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
206 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
206 commits_by_month = [0] * 12
207 commits_by_month = [0] * 12
207 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
208 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
208
209
209 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
210 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
210 changes_by_month = [0] * 12
211 changes_by_month = [0] * 12
211 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
212 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
212
213
213 fields = []
214 fields = []
214 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
215 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
215 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
216 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
216
217
217 graph = SVG::Graph::Bar.new(
218 graph = SVG::Graph::Bar.new(
218 :height => 300,
219 :height => 300,
219 :width => 500,
220 :width => 500,
220 :fields => fields.reverse,
221 :fields => fields.reverse,
221 :stack => :side,
222 :stack => :side,
222 :scale_integers => true,
223 :scale_integers => true,
223 :step_x_labels => 2,
224 :step_x_labels => 2,
224 :show_data_values => false,
225 :show_data_values => false,
225 :graph_title => l(:label_commits_per_month),
226 :graph_title => l(:label_commits_per_month),
226 :show_graph_title => true
227 :show_graph_title => true
227 )
228 )
228
229
229 graph.add_data(
230 graph.add_data(
230 :data => commits_by_month[0..11].reverse,
231 :data => commits_by_month[0..11].reverse,
231 :title => l(:label_revision_plural)
232 :title => l(:label_revision_plural)
232 )
233 )
233
234
234 graph.add_data(
235 graph.add_data(
235 :data => changes_by_month[0..11].reverse,
236 :data => changes_by_month[0..11].reverse,
236 :title => l(:label_change_plural)
237 :title => l(:label_change_plural)
237 )
238 )
238
239
239 graph.burn
240 graph.burn
240 end
241 end
241
242
242 def graph_commits_per_author(repository)
243 def graph_commits_per_author(repository)
243 commits_by_author = repository.changesets.count(:all, :group => :committer)
244 commits_by_author = repository.changesets.count(:all, :group => :committer)
244 commits_by_author.sort! {|x, y| x.last <=> y.last}
245 commits_by_author.sort! {|x, y| x.last <=> y.last}
245
246
246 changes_by_author = repository.changes.count(:all, :group => :committer)
247 changes_by_author = repository.changes.count(:all, :group => :committer)
247 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
248 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
248
249
249 fields = commits_by_author.collect {|r| r.first}
250 fields = commits_by_author.collect {|r| r.first}
250 commits_data = commits_by_author.collect {|r| r.last}
251 commits_data = commits_by_author.collect {|r| r.last}
251 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
252 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
252
253
253 fields = fields + [""]*(10 - fields.length) if fields.length<10
254 fields = fields + [""]*(10 - fields.length) if fields.length<10
254 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
255 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
255 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
256 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
256
257
257 graph = SVG::Graph::BarHorizontal.new(
258 graph = SVG::Graph::BarHorizontal.new(
258 :height => 300,
259 :height => 300,
259 :width => 500,
260 :width => 500,
260 :fields => fields,
261 :fields => fields,
261 :stack => :side,
262 :stack => :side,
262 :scale_integers => true,
263 :scale_integers => true,
263 :show_data_values => false,
264 :show_data_values => false,
264 :rotate_y_labels => false,
265 :rotate_y_labels => false,
265 :graph_title => l(:label_commits_per_author),
266 :graph_title => l(:label_commits_per_author),
266 :show_graph_title => true
267 :show_graph_title => true
267 )
268 )
268
269
269 graph.add_data(
270 graph.add_data(
270 :data => commits_data,
271 :data => commits_data,
271 :title => l(:label_revision_plural)
272 :title => l(:label_revision_plural)
272 )
273 )
273
274
274 graph.add_data(
275 graph.add_data(
275 :data => changes_data,
276 :data => changes_data,
276 :title => l(:label_change_plural)
277 :title => l(:label_change_plural)
277 )
278 )
278
279
279 graph.burn
280 graph.burn
280 end
281 end
281
282
282 end
283 end
283
284
284 class Date
285 class Date
285 def months_ago(date = Date.today)
286 def months_ago(date = Date.today)
286 (date.year - self.year)*12 + (date.month - self.month)
287 (date.year - self.year)*12 + (date.month - self.month)
287 end
288 end
288
289
289 def weeks_ago(date = Date.today)
290 def weeks_ago(date = Date.today)
290 (date.year - self.year)*52 + (date.cweek - self.cweek)
291 (date.year - self.year)*52 + (date.cweek - self.cweek)
291 end
292 end
292 end
293 end
293
294
294 class String
295 class String
295 def with_leading_slash
296 def with_leading_slash
296 starts_with?('/') ? self : "/#{self}"
297 starts_with?('/') ? self : "/#{self}"
297 end
298 end
298 end
299 end
@@ -1,471 +1,487
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 html_title(*args)
159 def html_title(*args)
160 if args.empty?
160 if args.empty?
161 title = []
161 title = []
162 title << @project.name if @project
162 title << @project.name if @project
163 title += @html_title if @html_title
163 title += @html_title if @html_title
164 title << Setting.app_title
164 title << Setting.app_title
165 title.compact.join(' - ')
165 title.compact.join(' - ')
166 else
166 else
167 @html_title ||= []
167 @html_title ||= []
168 @html_title += args
168 @html_title += args
169 end
169 end
170 end
170 end
171
171
172 def accesskey(s)
172 def accesskey(s)
173 Redmine::AccessKeys.key_for s
173 Redmine::AccessKeys.key_for s
174 end
174 end
175
175
176 # Formats text according to system settings.
176 # Formats text according to system settings.
177 # 2 ways to call this method:
177 # 2 ways to call this method:
178 # * with a String: textilizable(text, options)
178 # * with a String: textilizable(text, options)
179 # * with an object and one of its attribute: textilizable(issue, :description, options)
179 # * with an object and one of its attribute: textilizable(issue, :description, options)
180 def textilizable(*args)
180 def textilizable(*args)
181 options = args.last.is_a?(Hash) ? args.pop : {}
181 options = args.last.is_a?(Hash) ? args.pop : {}
182 case args.size
182 case args.size
183 when 1
183 when 1
184 obj = nil
184 obj = nil
185 text = args.shift
185 text = args.shift
186 when 2
186 when 2
187 obj = args.shift
187 obj = args.shift
188 text = obj.send(args.shift).to_s
188 text = obj.send(args.shift).to_s
189 else
189 else
190 raise ArgumentError, 'invalid arguments to textilizable'
190 raise ArgumentError, 'invalid arguments to textilizable'
191 end
191 end
192 return '' if text.blank?
192 return '' if text.blank?
193
193
194 only_path = options.delete(:only_path) == false ? false : true
194 only_path = options.delete(:only_path) == false ? false : true
195
195
196 # when using an image link, try to use an attachment, if possible
196 # when using an image link, try to use an attachment, if possible
197 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
197 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
198
198
199 if attachments
199 if attachments
200 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
200 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
201 style = $1
201 style = $1
202 filename = $6
202 filename = $6
203 rf = Regexp.new(filename, Regexp::IGNORECASE)
203 rf = Regexp.new(filename, Regexp::IGNORECASE)
204 # search for the picture in attachments
204 # search for the picture in attachments
205 if found = attachments.detect { |att| att.filename =~ rf }
205 if found = attachments.detect { |att| att.filename =~ rf }
206 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found.id
206 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found.id
207 "!#{style}#{image_url}!"
207 "!#{style}#{image_url}!"
208 else
208 else
209 "!#{style}#{filename}!"
209 "!#{style}#{filename}!"
210 end
210 end
211 end
211 end
212 end
212 end
213
213
214 text = (Setting.text_formatting == 'textile') ?
214 text = (Setting.text_formatting == 'textile') ?
215 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
215 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
216 simple_format(auto_link(h(text)))
216 simple_format(auto_link(h(text)))
217
217
218 # different methods for formatting wiki links
218 # different methods for formatting wiki links
219 case options[:wiki_links]
219 case options[:wiki_links]
220 when :local
220 when :local
221 # used for local links to html files
221 # used for local links to html files
222 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
222 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
223 when :anchor
223 when :anchor
224 # used for single-file wiki export
224 # used for single-file wiki export
225 format_wiki_link = Proc.new {|project, title| "##{title}" }
225 format_wiki_link = Proc.new {|project, title| "##{title}" }
226 else
226 else
227 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
227 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
228 end
228 end
229
229
230 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
230 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
231
231
232 # Wiki links
232 # Wiki links
233 #
233 #
234 # Examples:
234 # Examples:
235 # [[mypage]]
235 # [[mypage]]
236 # [[mypage|mytext]]
236 # [[mypage|mytext]]
237 # wiki links can refer other project wikis, using project name or identifier:
237 # wiki links can refer other project wikis, using project name or identifier:
238 # [[project:]] -> wiki starting page
238 # [[project:]] -> wiki starting page
239 # [[project:|mytext]]
239 # [[project:|mytext]]
240 # [[project:mypage]]
240 # [[project:mypage]]
241 # [[project:mypage|mytext]]
241 # [[project:mypage|mytext]]
242 text = text.gsub(/(!)?(\[\[([^\]\|]+)(\|([^\]\|]+))?\]\])/) do |m|
242 text = text.gsub(/(!)?(\[\[([^\]\|]+)(\|([^\]\|]+))?\]\])/) do |m|
243 link_project = project
243 link_project = project
244 esc, all, page, title = $1, $2, $3, $5
244 esc, all, page, title = $1, $2, $3, $5
245 if esc.nil?
245 if esc.nil?
246 if page =~ /^([^\:]+)\:(.*)$/
246 if page =~ /^([^\:]+)\:(.*)$/
247 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
247 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
248 page = $2
248 page = $2
249 title ||= $1 if page.blank?
249 title ||= $1 if page.blank?
250 end
250 end
251
251
252 if link_project && link_project.wiki
252 if link_project && link_project.wiki
253 # check if page exists
253 # check if page exists
254 wiki_page = link_project.wiki.find_page(page)
254 wiki_page = link_project.wiki.find_page(page)
255 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
255 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
256 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
256 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
257 else
257 else
258 # project or wiki doesn't exist
258 # project or wiki doesn't exist
259 title || page
259 title || page
260 end
260 end
261 else
261 else
262 all
262 all
263 end
263 end
264 end
264 end
265
265
266 # Redmine links
266 # Redmine links
267 #
267 #
268 # Examples:
268 # Examples:
269 # Issues:
269 # Issues:
270 # #52 -> Link to issue #52
270 # #52 -> Link to issue #52
271 # Changesets:
271 # Changesets:
272 # r52 -> Link to revision 52
272 # r52 -> Link to revision 52
273 # commit:a85130f -> Link to scmid starting with a85130f
273 # commit:a85130f -> Link to scmid starting with a85130f
274 # Documents:
274 # Documents:
275 # document#17 -> Link to document with id 17
275 # document#17 -> Link to document with id 17
276 # document:Greetings -> Link to the document with title "Greetings"
276 # document:Greetings -> Link to the document with title "Greetings"
277 # document:"Some document" -> Link to the document with title "Some document"
277 # document:"Some document" -> Link to the document with title "Some document"
278 # Versions:
278 # Versions:
279 # version#3 -> Link to version with id 3
279 # version#3 -> Link to version with id 3
280 # version:1.0.0 -> Link to version named "1.0.0"
280 # version:1.0.0 -> Link to version named "1.0.0"
281 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
281 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
282 # Attachments:
282 # Attachments:
283 # attachment:file.zip -> Link to the attachment of the current object named file.zip
283 # attachment:file.zip -> Link to the attachment of the current object named file.zip
284 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
284 # Source files:
285 # source:some/file -> Link to the file located at /some/file in the project's repository
286 # source:some/file@52 -> Link to the file's revision 52
287 # source:some/file#L120 -> Link to line 120 of the file
288 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
289 # export:some/file -> Force the download of the file
290 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
285 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
291 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
286 link = nil
292 link = nil
287 if esc.nil?
293 if esc.nil?
288 if prefix.nil? && sep == 'r'
294 if prefix.nil? && sep == 'r'
289 if project && (changeset = project.changesets.find_by_revision(oid))
295 if project && (changeset = project.changesets.find_by_revision(oid))
290 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid},
296 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
291 :class => 'changeset',
297 :class => 'changeset',
292 :title => truncate(changeset.comments, 100))
298 :title => truncate(changeset.comments, 100))
293 end
299 end
294 elsif sep == '#'
300 elsif sep == '#'
295 oid = oid.to_i
301 oid = oid.to_i
296 case prefix
302 case prefix
297 when nil
303 when nil
298 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
304 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
299 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
305 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
300 :class => (issue.closed? ? 'issue closed' : 'issue'),
306 :class => (issue.closed? ? 'issue closed' : 'issue'),
301 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
307 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
302 link = content_tag('del', link) if issue.closed?
308 link = content_tag('del', link) if issue.closed?
303 end
309 end
304 when 'document'
310 when 'document'
305 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
311 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
306 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
312 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
307 :class => 'document'
313 :class => 'document'
308 end
314 end
309 when 'version'
315 when 'version'
310 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
316 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
311 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
317 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
312 :class => 'version'
318 :class => 'version'
313 end
319 end
314 end
320 end
315 elsif sep == ':'
321 elsif sep == ':'
316 # removes the double quotes if any
322 # removes the double quotes if any
317 name = oid.gsub(%r{^"(.*)"$}, "\\1")
323 name = oid.gsub(%r{^"(.*)"$}, "\\1")
318 case prefix
324 case prefix
319 when 'document'
325 when 'document'
320 if project && document = project.documents.find_by_title(name)
326 if project && document = project.documents.find_by_title(name)
321 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
327 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
322 :class => 'document'
328 :class => 'document'
323 end
329 end
324 when 'version'
330 when 'version'
325 if project && version = project.versions.find_by_name(name)
331 if project && version = project.versions.find_by_name(name)
326 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
332 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
327 :class => 'version'
333 :class => 'version'
328 end
334 end
329 when 'commit'
335 when 'commit'
330 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
336 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
331 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project.id, :rev => changeset.revision}, :class => 'changeset', :title => truncate(changeset.comments, 100)
337 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)
338 end
339 when 'source', 'export'
340 if project && project.repository
341 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
342 path, rev, anchor = $1, $3, $5
343 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path,
344 :rev => rev,
345 :anchor => anchor,
346 :format => (prefix == 'export' ? 'raw' : nil)},
347 :class => (prefix == 'export' ? 'source download' : 'source')
332 end
348 end
333 when 'attachment'
349 when 'attachment'
334 if attachments && attachment = attachments.detect {|a| a.filename == name }
350 if attachments && attachment = attachments.detect {|a| a.filename == name }
335 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
351 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
336 :class => 'attachment'
352 :class => 'attachment'
337 end
353 end
338 end
354 end
339 end
355 end
340 end
356 end
341 leading + (link || "#{prefix}#{sep}#{oid}")
357 leading + (link || "#{prefix}#{sep}#{oid}")
342 end
358 end
343
359
344 text
360 text
345 end
361 end
346
362
347 # Same as Rails' simple_format helper without using paragraphs
363 # Same as Rails' simple_format helper without using paragraphs
348 def simple_format_without_paragraph(text)
364 def simple_format_without_paragraph(text)
349 text.to_s.
365 text.to_s.
350 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
366 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
351 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
367 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
352 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
368 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
353 end
369 end
354
370
355 def error_messages_for(object_name, options = {})
371 def error_messages_for(object_name, options = {})
356 options = options.symbolize_keys
372 options = options.symbolize_keys
357 object = instance_variable_get("@#{object_name}")
373 object = instance_variable_get("@#{object_name}")
358 if object && !object.errors.empty?
374 if object && !object.errors.empty?
359 # build full_messages here with controller current language
375 # build full_messages here with controller current language
360 full_messages = []
376 full_messages = []
361 object.errors.each do |attr, msg|
377 object.errors.each do |attr, msg|
362 next if msg.nil?
378 next if msg.nil?
363 msg = msg.first if msg.is_a? Array
379 msg = msg.first if msg.is_a? Array
364 if attr == "base"
380 if attr == "base"
365 full_messages << l(msg)
381 full_messages << l(msg)
366 else
382 else
367 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
383 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
368 end
384 end
369 end
385 end
370 # retrieve custom values error messages
386 # retrieve custom values error messages
371 if object.errors[:custom_values]
387 if object.errors[:custom_values]
372 object.custom_values.each do |v|
388 object.custom_values.each do |v|
373 v.errors.each do |attr, msg|
389 v.errors.each do |attr, msg|
374 next if msg.nil?
390 next if msg.nil?
375 msg = msg.first if msg.is_a? Array
391 msg = msg.first if msg.is_a? Array
376 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
392 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
377 end
393 end
378 end
394 end
379 end
395 end
380 content_tag("div",
396 content_tag("div",
381 content_tag(
397 content_tag(
382 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
398 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
383 ) +
399 ) +
384 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
400 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
385 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
401 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
386 )
402 )
387 else
403 else
388 ""
404 ""
389 end
405 end
390 end
406 end
391
407
392 def lang_options_for_select(blank=true)
408 def lang_options_for_select(blank=true)
393 (blank ? [["(auto)", ""]] : []) +
409 (blank ? [["(auto)", ""]] : []) +
394 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
410 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
395 end
411 end
396
412
397 def label_tag_for(name, option_tags = nil, options = {})
413 def label_tag_for(name, option_tags = nil, options = {})
398 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
414 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
399 content_tag("label", label_text)
415 content_tag("label", label_text)
400 end
416 end
401
417
402 def labelled_tabular_form_for(name, object, options, &proc)
418 def labelled_tabular_form_for(name, object, options, &proc)
403 options[:html] ||= {}
419 options[:html] ||= {}
404 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
420 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
405 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
421 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
406 end
422 end
407
423
408 def check_all_links(form_name)
424 def check_all_links(form_name)
409 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
425 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
410 " | " +
426 " | " +
411 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
427 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
412 end
428 end
413
429
414 def progress_bar(pcts, options={})
430 def progress_bar(pcts, options={})
415 pcts = [pcts, pcts] unless pcts.is_a?(Array)
431 pcts = [pcts, pcts] unless pcts.is_a?(Array)
416 pcts[1] = pcts[1] - pcts[0]
432 pcts[1] = pcts[1] - pcts[0]
417 pcts << (100 - pcts[1] - pcts[0])
433 pcts << (100 - pcts[1] - pcts[0])
418 width = options[:width] || '100px;'
434 width = options[:width] || '100px;'
419 legend = options[:legend] || ''
435 legend = options[:legend] || ''
420 content_tag('table',
436 content_tag('table',
421 content_tag('tr',
437 content_tag('tr',
422 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
438 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
423 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
439 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
424 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
440 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
425 ), :class => 'progress', :style => "width: #{width};") +
441 ), :class => 'progress', :style => "width: #{width};") +
426 content_tag('p', legend, :class => 'pourcent')
442 content_tag('p', legend, :class => 'pourcent')
427 end
443 end
428
444
429 def context_menu_link(name, url, options={})
445 def context_menu_link(name, url, options={})
430 options[:class] ||= ''
446 options[:class] ||= ''
431 if options.delete(:selected)
447 if options.delete(:selected)
432 options[:class] << ' icon-checked disabled'
448 options[:class] << ' icon-checked disabled'
433 options[:disabled] = true
449 options[:disabled] = true
434 end
450 end
435 if options.delete(:disabled)
451 if options.delete(:disabled)
436 options.delete(:method)
452 options.delete(:method)
437 options.delete(:confirm)
453 options.delete(:confirm)
438 options.delete(:onclick)
454 options.delete(:onclick)
439 options[:class] << ' disabled'
455 options[:class] << ' disabled'
440 url = '#'
456 url = '#'
441 end
457 end
442 link_to name, url, options
458 link_to name, url, options
443 end
459 end
444
460
445 def calendar_for(field_id)
461 def calendar_for(field_id)
446 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
462 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
447 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
463 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
448 end
464 end
449
465
450 def wikitoolbar_for(field_id)
466 def wikitoolbar_for(field_id)
451 return '' unless Setting.text_formatting == 'textile'
467 return '' unless Setting.text_formatting == 'textile'
452
468
453 help_link = l(:setting_text_formatting) + ': ' +
469 help_link = l(:setting_text_formatting) + ': ' +
454 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
470 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
455 :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;")
471 :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;")
456
472
457 javascript_include_tag('jstoolbar/jstoolbar') +
473 javascript_include_tag('jstoolbar/jstoolbar') +
458 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
474 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
459 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
475 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
460 end
476 end
461
477
462 def content_for(name, content = nil, &block)
478 def content_for(name, content = nil, &block)
463 @has_content ||= {}
479 @has_content ||= {}
464 @has_content[name] = true
480 @has_content[name] = true
465 super(name, content, &block)
481 super(name, content, &block)
466 end
482 end
467
483
468 def has_content?(name)
484 def has_content?(name)
469 (@has_content && @has_content[name]) || false
485 (@has_content && @has_content[name]) || false
470 end
486 end
471 end
487 end
@@ -1,20 +1,17
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
2
2
3 <div class="autoscroll">
3 <div class="autoscroll">
4 <table class="filecontent CodeRay">
4 <table class="filecontent CodeRay">
5 <tbody>
5 <tbody>
6 <% line_num = 1 %>
6 <% line_num = 1 %>
7 <% syntax_highlight(@path, to_utf8(@content)).each_line do |line| %>
7 <% syntax_highlight(@path, to_utf8(@content)).each_line do |line| %>
8 <tr>
8 <tr><th class="line-num" id="L<%= line_num %>"><%= line_num %></th><td class="line-code"><pre><%= line %></pre></td></tr>
9 <th class="line-num"><%= line_num %></th>
9 <% line_num += 1 %>
10 <td class="line-code"><pre><%= line %></pre></td>
10 <% end %>
11 </tr>
11 </tbody>
12 <% line_num += 1 %>
13 <% end %>
14 </tbody>
15 </table>
12 </table>
16 </div>
13 </div>
17
14
18 <% content_for :header_tags do %>
15 <% content_for :header_tags do %>
19 <%= stylesheet_link_tag "scm" %>
16 <%= stylesheet_link_tag "scm" %>
20 <% end %>
17 <% end %>
@@ -1,155 +1,168
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 require File.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents
24
24
25 def setup
25 def setup
26 super
26 super
27 end
27 end
28
28
29 def test_auto_links
29 def test_auto_links
30 to_test = {
30 to_test = {
31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
34 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
34 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
36 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
36 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
38 }
38 }
39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
40 end
40 end
41
41
42 def test_auto_mailto
42 def test_auto_mailto
43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
44 textilizable('test@foo.bar')
44 textilizable('test@foo.bar')
45 end
45 end
46
46
47 def test_inline_images
47 def test_inline_images
48 to_test = {
48 to_test = {
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
53 }
53 }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 end
55 end
56
56
57 def test_textile_external_links
57 def test_textile_external_links
58 to_test = {
58 to_test = {
59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>'
61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>'
62 }
62 }
63 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
63 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
64 end
64 end
65
65
66 def test_redmine_links
66 def test_redmine_links
67 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
67 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
68 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
68 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
69
69
70 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 1, :rev => 1},
70 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
71 :class => 'changeset', :title => 'My very first commit')
71 :class => 'changeset', :title => 'My very first commit')
72
72
73 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
73 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
74 :class => 'document')
74 :class => 'document')
75
75
76 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
76 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
77 :class => 'version')
77 :class => 'version')
78
79 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => 'some/file'}
78
80
79 to_test = {
81 to_test = {
80 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
82 # tickets
81 'r1' => changeset_link,
83 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
82 'document#1' => document_link,
84 # changesets
83 'document:"Test document"' => document_link,
85 'r1' => changeset_link,
84 'version#2' => version_link,
86 # documents
85 'version:1.0' => version_link,
87 'document#1' => document_link,
86 'version:"1.0"' => version_link,
88 'document:"Test document"' => document_link,
89 # versions
90 'version#2' => version_link,
91 'version:1.0' => version_link,
92 'version:"1.0"' => version_link,
93 # source
94 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
95 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
96 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
97 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
98 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
87 # escaping
99 # escaping
88 '!#3.' => '#3.',
100 '!#3.' => '#3.',
89 '!r1' => 'r1',
101 '!r1' => 'r1',
90 '!document#1' => 'document#1',
102 '!document#1' => 'document#1',
91 '!document:"Test document"' => 'document:"Test document"',
103 '!document:"Test document"' => 'document:"Test document"',
92 '!version#2' => 'version#2',
104 '!version#2' => 'version#2',
93 '!version:1.0' => 'version:1.0',
105 '!version:1.0' => 'version:1.0',
94 '!version:"1.0"' => 'version:"1.0"',
106 '!version:"1.0"' => 'version:"1.0"',
107 '!source:/some/file' => 'source:/some/file',
95 }
108 }
96 @project = Project.find(1)
109 @project = Project.find(1)
97 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
110 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
98 end
111 end
99
112
100 def test_wiki_links
113 def test_wiki_links
101 to_test = {
114 to_test = {
102 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
115 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
103 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
116 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
104 # page that doesn't exist
117 # page that doesn't exist
105 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
118 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
106 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
119 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
107 # link to another project wiki
120 # link to another project wiki
108 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
121 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
109 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
122 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
110 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
123 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
111 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
124 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
112 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
125 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
113 # escaping
126 # escaping
114 '![[Another page|Page]]' => '[[Another page|Page]]',
127 '![[Another page|Page]]' => '[[Another page|Page]]',
115 }
128 }
116 @project = Project.find(1)
129 @project = Project.find(1)
117 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
130 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
118 end
131 end
119
132
120 def test_macro_hello_world
133 def test_macro_hello_world
121 text = "{{hello_world}}"
134 text = "{{hello_world}}"
122 assert textilizable(text).match(/Hello world!/)
135 assert textilizable(text).match(/Hello world!/)
123 # escaping
136 # escaping
124 text = "!{{hello_world}}"
137 text = "!{{hello_world}}"
125 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
138 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
126 end
139 end
127
140
128 def test_date_format_default
141 def test_date_format_default
129 today = Date.today
142 today = Date.today
130 Setting.date_format = ''
143 Setting.date_format = ''
131 assert_equal l_date(today), format_date(today)
144 assert_equal l_date(today), format_date(today)
132 end
145 end
133
146
134 def test_date_format
147 def test_date_format
135 today = Date.today
148 today = Date.today
136 Setting.date_format = '%d %m %Y'
149 Setting.date_format = '%d %m %Y'
137 assert_equal today.strftime('%d %m %Y'), format_date(today)
150 assert_equal today.strftime('%d %m %Y'), format_date(today)
138 end
151 end
139
152
140 def test_time_format_default
153 def test_time_format_default
141 now = Time.now
154 now = Time.now
142 Setting.date_format = ''
155 Setting.date_format = ''
143 Setting.time_format = ''
156 Setting.time_format = ''
144 assert_equal l_datetime(now), format_time(now)
157 assert_equal l_datetime(now), format_time(now)
145 assert_equal l_time(now), format_time(now, false)
158 assert_equal l_time(now), format_time(now, false)
146 end
159 end
147
160
148 def test_time_format
161 def test_time_format
149 now = Time.now
162 now = Time.now
150 Setting.date_format = '%d %m %Y'
163 Setting.date_format = '%d %m %Y'
151 Setting.time_format = '%H %M'
164 Setting.time_format = '%H %M'
152 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
165 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
153 assert_equal now.strftime('%H %M'), format_time(now, false)
166 assert_equal now.strftime('%H %M'), format_time(now, false)
154 end
167 end
155 end
168 end
@@ -1,119 +1,119
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 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class MailerTest < Test::Unit::TestCase
20 class MailerTest < Test::Unit::TestCase
21 fixtures :projects, :issues, :users, :members, :documents, :attachments, :news, :tokens, :journals, :journal_details, :changesets, :trackers, :issue_statuses, :enumerations
21 fixtures :projects, :issues, :users, :members, :documents, :attachments, :news, :tokens, :journals, :journal_details, :changesets, :trackers, :issue_statuses, :enumerations
22
22
23 def test_generated_links_in_emails
23 def test_generated_links_in_emails
24 ActionMailer::Base.deliveries.clear
24 ActionMailer::Base.deliveries.clear
25 Setting.host_name = 'mydomain.foo'
25 Setting.host_name = 'mydomain.foo'
26 Setting.protocol = 'https'
26 Setting.protocol = 'https'
27
27
28 journal = Journal.find(2)
28 journal = Journal.find(2)
29 assert Mailer.deliver_issue_edit(journal)
29 assert Mailer.deliver_issue_edit(journal)
30
30
31 mail = ActionMailer::Base.deliveries.last
31 mail = ActionMailer::Base.deliveries.last
32 assert_kind_of TMail::Mail, mail
32 assert_kind_of TMail::Mail, mail
33 # link to the main ticket
33 # link to the main ticket
34 assert mail.body.include?('<a href="https://mydomain.foo/issues/show/1">Bug #1: Can\'t print recipes</a>')
34 assert mail.body.include?('<a href="https://mydomain.foo/issues/show/1">Bug #1: Can\'t print recipes</a>')
35
35
36 # link to a referenced ticket
36 # link to a referenced ticket
37 assert mail.body.include?('<a href="https://mydomain.foo/issues/show/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
37 assert mail.body.include?('<a href="https://mydomain.foo/issues/show/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
38 # link to a changeset
38 # link to a changeset
39 assert mail.body.include?('<a href="https://mydomain.foo/repositories/revision/1?rev=2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
39 assert mail.body.include?('<a href="https://mydomain.foo/repositories/revision/ecookbook?rev=2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
40 end
40 end
41
41
42 # test mailer methods for each language
42 # test mailer methods for each language
43 def test_issue_add
43 def test_issue_add
44 issue = Issue.find(1)
44 issue = Issue.find(1)
45 GLoc.valid_languages.each do |lang|
45 GLoc.valid_languages.each do |lang|
46 Setting.default_language = lang.to_s
46 Setting.default_language = lang.to_s
47 assert Mailer.deliver_issue_add(issue)
47 assert Mailer.deliver_issue_add(issue)
48 end
48 end
49 end
49 end
50
50
51 def test_issue_edit
51 def test_issue_edit
52 journal = Journal.find(1)
52 journal = Journal.find(1)
53 GLoc.valid_languages.each do |lang|
53 GLoc.valid_languages.each do |lang|
54 Setting.default_language = lang.to_s
54 Setting.default_language = lang.to_s
55 assert Mailer.deliver_issue_edit(journal)
55 assert Mailer.deliver_issue_edit(journal)
56 end
56 end
57 end
57 end
58
58
59 def test_document_added
59 def test_document_added
60 document = Document.find(1)
60 document = Document.find(1)
61 GLoc.valid_languages.each do |lang|
61 GLoc.valid_languages.each do |lang|
62 Setting.default_language = lang.to_s
62 Setting.default_language = lang.to_s
63 assert Mailer.deliver_document_added(document)
63 assert Mailer.deliver_document_added(document)
64 end
64 end
65 end
65 end
66
66
67 def test_attachments_added
67 def test_attachments_added
68 attachements = [ Attachment.find_by_container_type('Document') ]
68 attachements = [ Attachment.find_by_container_type('Document') ]
69 GLoc.valid_languages.each do |lang|
69 GLoc.valid_languages.each do |lang|
70 Setting.default_language = lang.to_s
70 Setting.default_language = lang.to_s
71 assert Mailer.deliver_attachments_added(attachements)
71 assert Mailer.deliver_attachments_added(attachements)
72 end
72 end
73 end
73 end
74
74
75 def test_news_added
75 def test_news_added
76 news = News.find(:first)
76 news = News.find(:first)
77 GLoc.valid_languages.each do |lang|
77 GLoc.valid_languages.each do |lang|
78 Setting.default_language = lang.to_s
78 Setting.default_language = lang.to_s
79 assert Mailer.deliver_news_added(news)
79 assert Mailer.deliver_news_added(news)
80 end
80 end
81 end
81 end
82
82
83 def test_message_posted
83 def test_message_posted
84 message = Message.find(:first)
84 message = Message.find(:first)
85 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
85 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
86 recipients = recipients.compact.uniq
86 recipients = recipients.compact.uniq
87 GLoc.valid_languages.each do |lang|
87 GLoc.valid_languages.each do |lang|
88 Setting.default_language = lang.to_s
88 Setting.default_language = lang.to_s
89 assert Mailer.deliver_message_posted(message, recipients)
89 assert Mailer.deliver_message_posted(message, recipients)
90 end
90 end
91 end
91 end
92
92
93 def test_account_information
93 def test_account_information
94 user = User.find(:first)
94 user = User.find(:first)
95 GLoc.valid_languages.each do |lang|
95 GLoc.valid_languages.each do |lang|
96 user.update_attribute :language, lang.to_s
96 user.update_attribute :language, lang.to_s
97 user.reload
97 user.reload
98 assert Mailer.deliver_account_information(user, 'pAsswORd')
98 assert Mailer.deliver_account_information(user, 'pAsswORd')
99 end
99 end
100 end
100 end
101
101
102 def test_lost_password
102 def test_lost_password
103 token = Token.find(2)
103 token = Token.find(2)
104 GLoc.valid_languages.each do |lang|
104 GLoc.valid_languages.each do |lang|
105 token.user.update_attribute :language, lang.to_s
105 token.user.update_attribute :language, lang.to_s
106 token.reload
106 token.reload
107 assert Mailer.deliver_lost_password(token)
107 assert Mailer.deliver_lost_password(token)
108 end
108 end
109 end
109 end
110
110
111 def test_register
111 def test_register
112 token = Token.find(1)
112 token = Token.find(1)
113 GLoc.valid_languages.each do |lang|
113 GLoc.valid_languages.each do |lang|
114 token.user.update_attribute :language, lang.to_s
114 token.user.update_attribute :language, lang.to_s
115 token.reload
115 token.reload
116 assert Mailer.deliver_register(token)
116 assert Mailer.deliver_register(token)
117 end
117 end
118 end
118 end
119 end
119 end
General Comments 0
You need to be logged in to leave comments. Login now