##// END OF EJS Templates
Merged r4103 from trunk....
Eric Davis -
r4045:8bd0ff03a08a
parent child
Show More
@@ -1,857 +1,857
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 'forwardable'
18 require 'forwardable'
19 require 'cgi'
19 require 'cgi'
20
20
21 module ApplicationHelper
21 module ApplicationHelper
22 include Redmine::WikiFormatting::Macros::Definitions
22 include Redmine::WikiFormatting::Macros::Definitions
23 include Redmine::I18n
23 include Redmine::I18n
24 include GravatarHelper::PublicMethods
24 include GravatarHelper::PublicMethods
25
25
26 extend Forwardable
26 extend Forwardable
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28
28
29 # Return true if user is authorized for controller/action, otherwise false
29 # Return true if user is authorized for controller/action, otherwise false
30 def authorize_for(controller, action)
30 def authorize_for(controller, action)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
32 end
32 end
33
33
34 # Display a link if user is authorized
34 # Display a link if user is authorized
35 #
35 #
36 # @param [String] name Anchor text (passed to link_to)
36 # @param [String] name Anchor text (passed to link_to)
37 # @param [Hash, String] options Hash params or url for the link target (passed to link_to).
37 # @param [Hash, String] options Hash params or url for the link target (passed to link_to).
38 # This will checked by authorize_for to see if the user is authorized
38 # This will checked by authorize_for to see if the user is authorized
39 # @param [optional, Hash] html_options Options passed to link_to
39 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
40 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
41 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
42 if options.is_a?(String)
42 if options.is_a?(String)
43 begin
43 begin
44 route = ActionController::Routing::Routes.recognize_path(options.gsub(/\?.*/,''), :method => options[:method] || :get)
44 route = ActionController::Routing::Routes.recognize_path(options.gsub(/\?.*/,''), :method => options[:method] || :get)
45 link_controller = route[:controller]
45 link_controller = route[:controller]
46 link_action = route[:action]
46 link_action = route[:action]
47 rescue ActionController::RoutingError # Parse failed, not a route
47 rescue ActionController::RoutingError # Parse failed, not a route
48 link_controller, link_action = nil, nil
48 link_controller, link_action = nil, nil
49 end
49 end
50 else
50 else
51 link_controller = options[:controller] || params[:controller]
51 link_controller = options[:controller] || params[:controller]
52 link_action = options[:action]
52 link_action = options[:action]
53 end
53 end
54
54
55 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(link_controller, link_action)
55 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(link_controller, link_action)
56 end
56 end
57
57
58 # Display a link to remote if user is authorized
58 # Display a link to remote if user is authorized
59 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
59 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
60 url = options[:url] || {}
60 url = options[:url] || {}
61 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
61 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
62 end
62 end
63
63
64 # Displays a link to user's account page if active
64 # Displays a link to user's account page if active
65 def link_to_user(user, options={})
65 def link_to_user(user, options={})
66 if user.is_a?(User)
66 if user.is_a?(User)
67 name = h(user.name(options[:format]))
67 name = h(user.name(options[:format]))
68 if user.active?
68 if user.active?
69 link_to name, :controller => 'users', :action => 'show', :id => user
69 link_to name, :controller => 'users', :action => 'show', :id => user
70 else
70 else
71 name
71 name
72 end
72 end
73 else
73 else
74 h(user.to_s)
74 h(user.to_s)
75 end
75 end
76 end
76 end
77
77
78 # Displays a link to +issue+ with its subject.
78 # Displays a link to +issue+ with its subject.
79 # Examples:
79 # Examples:
80 #
80 #
81 # link_to_issue(issue) # => Defect #6: This is the subject
81 # link_to_issue(issue) # => Defect #6: This is the subject
82 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
82 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
83 # link_to_issue(issue, :subject => false) # => Defect #6
83 # link_to_issue(issue, :subject => false) # => Defect #6
84 # link_to_issue(issue, :project => true) # => Foo - Defect #6
84 # link_to_issue(issue, :project => true) # => Foo - Defect #6
85 #
85 #
86 def link_to_issue(issue, options={})
86 def link_to_issue(issue, options={})
87 title = nil
87 title = nil
88 subject = nil
88 subject = nil
89 if options[:subject] == false
89 if options[:subject] == false
90 title = truncate(issue.subject, :length => 60)
90 title = truncate(issue.subject, :length => 60)
91 else
91 else
92 subject = issue.subject
92 subject = issue.subject
93 if options[:truncate]
93 if options[:truncate]
94 subject = truncate(subject, :length => options[:truncate])
94 subject = truncate(subject, :length => options[:truncate])
95 end
95 end
96 end
96 end
97 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
97 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
98 :class => issue.css_classes,
98 :class => issue.css_classes,
99 :title => title
99 :title => title
100 s << ": #{h subject}" if subject
100 s << ": #{h subject}" if subject
101 s = "#{h issue.project} - " + s if options[:project]
101 s = "#{h issue.project} - " + s if options[:project]
102 s
102 s
103 end
103 end
104
104
105 # Generates a link to an attachment.
105 # Generates a link to an attachment.
106 # Options:
106 # Options:
107 # * :text - Link text (default to attachment filename)
107 # * :text - Link text (default to attachment filename)
108 # * :download - Force download (default: false)
108 # * :download - Force download (default: false)
109 def link_to_attachment(attachment, options={})
109 def link_to_attachment(attachment, options={})
110 text = options.delete(:text) || attachment.filename
110 text = options.delete(:text) || attachment.filename
111 action = options.delete(:download) ? 'download' : 'show'
111 action = options.delete(:download) ? 'download' : 'show'
112
112
113 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
113 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
114 end
114 end
115
115
116 # Generates a link to a SCM revision
116 # Generates a link to a SCM revision
117 # Options:
117 # Options:
118 # * :text - Link text (default to the formatted revision)
118 # * :text - Link text (default to the formatted revision)
119 def link_to_revision(revision, project, options={})
119 def link_to_revision(revision, project, options={})
120 text = options.delete(:text) || format_revision(revision)
120 text = options.delete(:text) || format_revision(revision)
121
121
122 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
122 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
123 end
123 end
124
124
125 # Generates a link to a project if active
125 # Generates a link to a project if active
126 # Examples:
126 # Examples:
127 #
127 #
128 # link_to_project(project) # => link to the specified project overview
128 # link_to_project(project) # => link to the specified project overview
129 # link_to_project(project, :action=>'settings') # => link to project settings
129 # link_to_project(project, :action=>'settings') # => link to project settings
130 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
130 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
131 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
131 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
132 #
132 #
133 def link_to_project(project, options={}, html_options = nil)
133 def link_to_project(project, options={}, html_options = nil)
134 if project.active?
134 if project.active?
135 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
135 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
136 link_to(h(project), url, html_options)
136 link_to(h(project), url, html_options)
137 else
137 else
138 h(project)
138 h(project)
139 end
139 end
140 end
140 end
141
141
142 def toggle_link(name, id, options={})
142 def toggle_link(name, id, options={})
143 onclick = "Element.toggle('#{id}'); "
143 onclick = "Element.toggle('#{id}'); "
144 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
144 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
145 onclick << "return false;"
145 onclick << "return false;"
146 link_to(name, "#", :onclick => onclick)
146 link_to(name, "#", :onclick => onclick)
147 end
147 end
148
148
149 def image_to_function(name, function, html_options = {})
149 def image_to_function(name, function, html_options = {})
150 html_options.symbolize_keys!
150 html_options.symbolize_keys!
151 tag(:input, html_options.merge({
151 tag(:input, html_options.merge({
152 :type => "image", :src => image_path(name),
152 :type => "image", :src => image_path(name),
153 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
153 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
154 }))
154 }))
155 end
155 end
156
156
157 def prompt_to_remote(name, text, param, url, html_options = {})
157 def prompt_to_remote(name, text, param, url, html_options = {})
158 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
158 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
159 link_to name, {}, html_options
159 link_to name, {}, html_options
160 end
160 end
161
161
162 def format_activity_title(text)
162 def format_activity_title(text)
163 h(truncate_single_line(text, :length => 100))
163 h(truncate_single_line(text, :length => 100))
164 end
164 end
165
165
166 def format_activity_day(date)
166 def format_activity_day(date)
167 date == Date.today ? l(:label_today).titleize : format_date(date)
167 date == Date.today ? l(:label_today).titleize : format_date(date)
168 end
168 end
169
169
170 def format_activity_description(text)
170 def format_activity_description(text)
171 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
171 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
172 end
172 end
173
173
174 def format_version_name(version)
174 def format_version_name(version)
175 if version.project == @project
175 if version.project == @project
176 h(version)
176 h(version)
177 else
177 else
178 h("#{version.project} - #{version}")
178 h("#{version.project} - #{version}")
179 end
179 end
180 end
180 end
181
181
182 def due_date_distance_in_words(date)
182 def due_date_distance_in_words(date)
183 if date
183 if date
184 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
184 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
185 end
185 end
186 end
186 end
187
187
188 def render_page_hierarchy(pages, node=nil)
188 def render_page_hierarchy(pages, node=nil)
189 content = ''
189 content = ''
190 if pages[node]
190 if pages[node]
191 content << "<ul class=\"pages-hierarchy\">\n"
191 content << "<ul class=\"pages-hierarchy\">\n"
192 pages[node].each do |page|
192 pages[node].each do |page|
193 content << "<li>"
193 content << "<li>"
194 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
194 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
195 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
195 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
196 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
196 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
197 content << "</li>\n"
197 content << "</li>\n"
198 end
198 end
199 content << "</ul>\n"
199 content << "</ul>\n"
200 end
200 end
201 content
201 content
202 end
202 end
203
203
204 # Renders flash messages
204 # Renders flash messages
205 def render_flash_messages
205 def render_flash_messages
206 s = ''
206 s = ''
207 flash.each do |k,v|
207 flash.each do |k,v|
208 s << content_tag('div', v, :class => "flash #{k}")
208 s << content_tag('div', v, :class => "flash #{k}")
209 end
209 end
210 s
210 s
211 end
211 end
212
212
213 # Renders tabs and their content
213 # Renders tabs and their content
214 def render_tabs(tabs)
214 def render_tabs(tabs)
215 if tabs.any?
215 if tabs.any?
216 render :partial => 'common/tabs', :locals => {:tabs => tabs}
216 render :partial => 'common/tabs', :locals => {:tabs => tabs}
217 else
217 else
218 content_tag 'p', l(:label_no_data), :class => "nodata"
218 content_tag 'p', l(:label_no_data), :class => "nodata"
219 end
219 end
220 end
220 end
221
221
222 # Renders the project quick-jump box
222 # Renders the project quick-jump box
223 def render_project_jump_box
223 def render_project_jump_box
224 # Retrieve them now to avoid a COUNT query
224 # Retrieve them now to avoid a COUNT query
225 projects = User.current.projects.all
225 projects = User.current.projects.all
226 if projects.any?
226 if projects.any?
227 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
227 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
228 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
228 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
229 '<option value="" disabled="disabled">---</option>'
229 '<option value="" disabled="disabled">---</option>'
230 s << project_tree_options_for_select(projects, :selected => @project) do |p|
230 s << project_tree_options_for_select(projects, :selected => @project) do |p|
231 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
231 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
232 end
232 end
233 s << '</select>'
233 s << '</select>'
234 s
234 s
235 end
235 end
236 end
236 end
237
237
238 def project_tree_options_for_select(projects, options = {})
238 def project_tree_options_for_select(projects, options = {})
239 s = ''
239 s = ''
240 project_tree(projects) do |project, level|
240 project_tree(projects) do |project, level|
241 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
241 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
242 tag_options = {:value => project.id}
242 tag_options = {:value => project.id}
243 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
243 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
244 tag_options[:selected] = 'selected'
244 tag_options[:selected] = 'selected'
245 else
245 else
246 tag_options[:selected] = nil
246 tag_options[:selected] = nil
247 end
247 end
248 tag_options.merge!(yield(project)) if block_given?
248 tag_options.merge!(yield(project)) if block_given?
249 s << content_tag('option', name_prefix + h(project), tag_options)
249 s << content_tag('option', name_prefix + h(project), tag_options)
250 end
250 end
251 s
251 s
252 end
252 end
253
253
254 # Yields the given block for each project with its level in the tree
254 # Yields the given block for each project with its level in the tree
255 def project_tree(projects, &block)
255 def project_tree(projects, &block)
256 ancestors = []
256 ancestors = []
257 projects.sort_by(&:lft).each do |project|
257 projects.sort_by(&:lft).each do |project|
258 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
258 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
259 ancestors.pop
259 ancestors.pop
260 end
260 end
261 yield project, ancestors.size
261 yield project, ancestors.size
262 ancestors << project
262 ancestors << project
263 end
263 end
264 end
264 end
265
265
266 def project_nested_ul(projects, &block)
266 def project_nested_ul(projects, &block)
267 s = ''
267 s = ''
268 if projects.any?
268 if projects.any?
269 ancestors = []
269 ancestors = []
270 projects.sort_by(&:lft).each do |project|
270 projects.sort_by(&:lft).each do |project|
271 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
271 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
272 s << "<ul>\n"
272 s << "<ul>\n"
273 else
273 else
274 ancestors.pop
274 ancestors.pop
275 s << "</li>"
275 s << "</li>"
276 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
276 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
277 ancestors.pop
277 ancestors.pop
278 s << "</ul></li>\n"
278 s << "</ul></li>\n"
279 end
279 end
280 end
280 end
281 s << "<li>"
281 s << "<li>"
282 s << yield(project).to_s
282 s << yield(project).to_s
283 ancestors << project
283 ancestors << project
284 end
284 end
285 s << ("</li></ul>\n" * ancestors.size)
285 s << ("</li></ul>\n" * ancestors.size)
286 end
286 end
287 s
287 s
288 end
288 end
289
289
290 def principals_check_box_tags(name, principals)
290 def principals_check_box_tags(name, principals)
291 s = ''
291 s = ''
292 principals.sort.each do |principal|
292 principals.sort.each do |principal|
293 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
293 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
294 end
294 end
295 s
295 s
296 end
296 end
297
297
298 # Truncates and returns the string as a single line
298 # Truncates and returns the string as a single line
299 def truncate_single_line(string, *args)
299 def truncate_single_line(string, *args)
300 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
300 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
301 end
301 end
302
302
303 # Truncates at line break after 250 characters or options[:length]
303 # Truncates at line break after 250 characters or options[:length]
304 def truncate_lines(string, options={})
304 def truncate_lines(string, options={})
305 length = options[:length] || 250
305 length = options[:length] || 250
306 if string.to_s =~ /\A(.{#{length}}.*?)$/m
306 if string.to_s =~ /\A(.{#{length}}.*?)$/m
307 "#{$1}..."
307 "#{$1}..."
308 else
308 else
309 string
309 string
310 end
310 end
311 end
311 end
312
312
313 def html_hours(text)
313 def html_hours(text)
314 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
314 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
315 end
315 end
316
316
317 def authoring(created, author, options={})
317 def authoring(created, author, options={})
318 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
318 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
319 end
319 end
320
320
321 def time_tag(time)
321 def time_tag(time)
322 text = distance_of_time_in_words(Time.now, time)
322 text = distance_of_time_in_words(Time.now, time)
323 if @project
323 if @project
324 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
324 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
325 else
325 else
326 content_tag('acronym', text, :title => format_time(time))
326 content_tag('acronym', text, :title => format_time(time))
327 end
327 end
328 end
328 end
329
329
330 def syntax_highlight(name, content)
330 def syntax_highlight(name, content)
331 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
331 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
332 end
332 end
333
333
334 def to_path_param(path)
334 def to_path_param(path)
335 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
335 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
336 end
336 end
337
337
338 def pagination_links_full(paginator, count=nil, options={})
338 def pagination_links_full(paginator, count=nil, options={})
339 page_param = options.delete(:page_param) || :page
339 page_param = options.delete(:page_param) || :page
340 per_page_links = options.delete(:per_page_links)
340 per_page_links = options.delete(:per_page_links)
341 url_param = params.dup
341 url_param = params.dup
342 # don't reuse query params if filters are present
342 # don't reuse query params if filters are present
343 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
343 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
344
344
345 html = ''
345 html = ''
346 if paginator.current.previous
346 if paginator.current.previous
347 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
347 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
348 end
348 end
349
349
350 html << (pagination_links_each(paginator, options) do |n|
350 html << (pagination_links_each(paginator, options) do |n|
351 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
351 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
352 end || '')
352 end || '')
353
353
354 if paginator.current.next
354 if paginator.current.next
355 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
355 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
356 end
356 end
357
357
358 unless count.nil?
358 unless count.nil?
359 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
359 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
360 if per_page_links != false && links = per_page_links(paginator.items_per_page)
360 if per_page_links != false && links = per_page_links(paginator.items_per_page)
361 html << " | #{links}"
361 html << " | #{links}"
362 end
362 end
363 end
363 end
364
364
365 html
365 html
366 end
366 end
367
367
368 def per_page_links(selected=nil)
368 def per_page_links(selected=nil)
369 url_param = params.dup
369 url_param = params.dup
370 url_param.clear if url_param.has_key?(:set_filter)
370 url_param.clear if url_param.has_key?(:set_filter)
371
371
372 links = Setting.per_page_options_array.collect do |n|
372 links = Setting.per_page_options_array.collect do |n|
373 n == selected ? n : link_to_remote(n, {:update => "content",
373 n == selected ? n : link_to_remote(n, {:update => "content",
374 :url => params.dup.merge(:per_page => n),
374 :url => params.dup.merge(:per_page => n),
375 :method => :get},
375 :method => :get},
376 {:href => url_for(url_param.merge(:per_page => n))})
376 {:href => url_for(url_param.merge(:per_page => n))})
377 end
377 end
378 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
378 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
379 end
379 end
380
380
381 def reorder_links(name, url)
381 def reorder_links(name, url)
382 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
382 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
383 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
383 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
384 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
384 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
385 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
385 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
386 end
386 end
387
387
388 def breadcrumb(*args)
388 def breadcrumb(*args)
389 elements = args.flatten
389 elements = args.flatten
390 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
390 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
391 end
391 end
392
392
393 def other_formats_links(&block)
393 def other_formats_links(&block)
394 concat('<p class="other-formats">' + l(:label_export_to))
394 concat('<p class="other-formats">' + l(:label_export_to))
395 yield Redmine::Views::OtherFormatsBuilder.new(self)
395 yield Redmine::Views::OtherFormatsBuilder.new(self)
396 concat('</p>')
396 concat('</p>')
397 end
397 end
398
398
399 def page_header_title
399 def page_header_title
400 if @project.nil? || @project.new_record?
400 if @project.nil? || @project.new_record?
401 h(Setting.app_title)
401 h(Setting.app_title)
402 else
402 else
403 b = []
403 b = []
404 ancestors = (@project.root? ? [] : @project.ancestors.visible)
404 ancestors = (@project.root? ? [] : @project.ancestors.visible)
405 if ancestors.any?
405 if ancestors.any?
406 root = ancestors.shift
406 root = ancestors.shift
407 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
407 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
408 if ancestors.size > 2
408 if ancestors.size > 2
409 b << '&#8230;'
409 b << '&#8230;'
410 ancestors = ancestors[-2, 2]
410 ancestors = ancestors[-2, 2]
411 end
411 end
412 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
412 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
413 end
413 end
414 b << h(@project)
414 b << h(@project)
415 b.join(' &#187; ')
415 b.join(' &#187; ')
416 end
416 end
417 end
417 end
418
418
419 def html_title(*args)
419 def html_title(*args)
420 if args.empty?
420 if args.empty?
421 title = []
421 title = []
422 title << @project.name if @project
422 title << @project.name if @project
423 title += @html_title if @html_title
423 title += @html_title if @html_title
424 title << Setting.app_title
424 title << Setting.app_title
425 title.select {|t| !t.blank? }.join(' - ')
425 title.select {|t| !t.blank? }.join(' - ')
426 else
426 else
427 @html_title ||= []
427 @html_title ||= []
428 @html_title += args
428 @html_title += args
429 end
429 end
430 end
430 end
431
431
432 # Returns the theme, controller name, and action as css classes for the
432 # Returns the theme, controller name, and action as css classes for the
433 # HTML body.
433 # HTML body.
434 def body_css_classes
434 def body_css_classes
435 css = []
435 css = []
436 if theme = Redmine::Themes.theme(Setting.ui_theme)
436 if theme = Redmine::Themes.theme(Setting.ui_theme)
437 css << 'theme-' + theme.name
437 css << 'theme-' + theme.name
438 end
438 end
439
439
440 css << 'controller-' + params[:controller]
440 css << 'controller-' + params[:controller]
441 css << 'action-' + params[:action]
441 css << 'action-' + params[:action]
442 css.join(' ')
442 css.join(' ')
443 end
443 end
444
444
445 def accesskey(s)
445 def accesskey(s)
446 Redmine::AccessKeys.key_for s
446 Redmine::AccessKeys.key_for s
447 end
447 end
448
448
449 # Formats text according to system settings.
449 # Formats text according to system settings.
450 # 2 ways to call this method:
450 # 2 ways to call this method:
451 # * with a String: textilizable(text, options)
451 # * with a String: textilizable(text, options)
452 # * with an object and one of its attribute: textilizable(issue, :description, options)
452 # * with an object and one of its attribute: textilizable(issue, :description, options)
453 def textilizable(*args)
453 def textilizable(*args)
454 options = args.last.is_a?(Hash) ? args.pop : {}
454 options = args.last.is_a?(Hash) ? args.pop : {}
455 case args.size
455 case args.size
456 when 1
456 when 1
457 obj = options[:object]
457 obj = options[:object]
458 text = args.shift
458 text = args.shift
459 when 2
459 when 2
460 obj = args.shift
460 obj = args.shift
461 attr = args.shift
461 attr = args.shift
462 text = obj.send(attr).to_s
462 text = obj.send(attr).to_s
463 else
463 else
464 raise ArgumentError, 'invalid arguments to textilizable'
464 raise ArgumentError, 'invalid arguments to textilizable'
465 end
465 end
466 return '' if text.blank?
466 return '' if text.blank?
467 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
467 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
468 only_path = options.delete(:only_path) == false ? false : true
468 only_path = options.delete(:only_path) == false ? false : true
469
469
470 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
470 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
471
471
472 parse_non_pre_blocks(text) do |text|
472 parse_non_pre_blocks(text) do |text|
473 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
473 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
474 send method_name, text, project, obj, attr, only_path, options
474 send method_name, text, project, obj, attr, only_path, options
475 end
475 end
476 end
476 end
477 end
477 end
478
478
479 def parse_non_pre_blocks(text)
479 def parse_non_pre_blocks(text)
480 s = StringScanner.new(text)
480 s = StringScanner.new(text)
481 tags = []
481 tags = []
482 parsed = ''
482 parsed = ''
483 while !s.eos?
483 while !s.eos?
484 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
484 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
485 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
485 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
486 if tags.empty?
486 if tags.empty?
487 yield text
487 yield text
488 end
488 end
489 parsed << text
489 parsed << text
490 if tag
490 if tag
491 if closing
491 if closing
492 if tags.last == tag.downcase
492 if tags.last == tag.downcase
493 tags.pop
493 tags.pop
494 end
494 end
495 else
495 else
496 tags << tag.downcase
496 tags << tag.downcase
497 end
497 end
498 parsed << full_tag
498 parsed << full_tag
499 end
499 end
500 end
500 end
501 # Close any non closing tags
501 # Close any non closing tags
502 while tag = tags.pop
502 while tag = tags.pop
503 parsed << "</#{tag}>"
503 parsed << "</#{tag}>"
504 end
504 end
505 parsed
505 parsed
506 end
506 end
507
507
508 def parse_inline_attachments(text, project, obj, attr, only_path, options)
508 def parse_inline_attachments(text, project, obj, attr, only_path, options)
509 # when using an image link, try to use an attachment, if possible
509 # when using an image link, try to use an attachment, if possible
510 if options[:attachments] || (obj && obj.respond_to?(:attachments))
510 if options[:attachments] || (obj && obj.respond_to?(:attachments))
511 attachments = nil
511 attachments = nil
512 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
512 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
513 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
513 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
514 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
514 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
515 # search for the picture in attachments
515 # search for the picture in attachments
516 if found = attachments.detect { |att| att.filename.downcase == filename }
516 if found = attachments.detect { |att| att.filename.downcase == filename }
517 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
517 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
518 desc = found.description.to_s.gsub('"', '')
518 desc = found.description.to_s.gsub('"', '')
519 if !desc.blank? && alttext.blank?
519 if !desc.blank? && alttext.blank?
520 alt = " title=\"#{desc}\" alt=\"#{desc}\""
520 alt = " title=\"#{desc}\" alt=\"#{desc}\""
521 end
521 end
522 "src=\"#{image_url}\"#{alt}"
522 "src=\"#{image_url}\"#{alt}"
523 else
523 else
524 m
524 m
525 end
525 end
526 end
526 end
527 end
527 end
528 end
528 end
529
529
530 # Wiki links
530 # Wiki links
531 #
531 #
532 # Examples:
532 # Examples:
533 # [[mypage]]
533 # [[mypage]]
534 # [[mypage|mytext]]
534 # [[mypage|mytext]]
535 # wiki links can refer other project wikis, using project name or identifier:
535 # wiki links can refer other project wikis, using project name or identifier:
536 # [[project:]] -> wiki starting page
536 # [[project:]] -> wiki starting page
537 # [[project:|mytext]]
537 # [[project:|mytext]]
538 # [[project:mypage]]
538 # [[project:mypage]]
539 # [[project:mypage|mytext]]
539 # [[project:mypage|mytext]]
540 def parse_wiki_links(text, project, obj, attr, only_path, options)
540 def parse_wiki_links(text, project, obj, attr, only_path, options)
541 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
541 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
542 link_project = project
542 link_project = project
543 esc, all, page, title = $1, $2, $3, $5
543 esc, all, page, title = $1, $2, $3, $5
544 if esc.nil?
544 if esc.nil?
545 if page =~ /^([^\:]+)\:(.*)$/
545 if page =~ /^([^\:]+)\:(.*)$/
546 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
546 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
547 page = $2
547 page = $2
548 title ||= $1 if page.blank?
548 title ||= $1 if page.blank?
549 end
549 end
550
550
551 if link_project && link_project.wiki
551 if link_project && link_project.wiki
552 # extract anchor
552 # extract anchor
553 anchor = nil
553 anchor = nil
554 if page =~ /^(.+?)\#(.+)$/
554 if page =~ /^(.+?)\#(.+)$/
555 page, anchor = $1, $2
555 page, anchor = $1, $2
556 end
556 end
557 # check if page exists
557 # check if page exists
558 wiki_page = link_project.wiki.find_page(page)
558 wiki_page = link_project.wiki.find_page(page)
559 url = case options[:wiki_links]
559 url = case options[:wiki_links]
560 when :local; "#{title}.html"
560 when :local; "#{title}.html"
561 when :anchor; "##{title}" # used for single-file wiki export
561 when :anchor; "##{title}" # used for single-file wiki export
562 else
562 else
563 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
563 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
564 end
564 end
565 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
565 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
566 else
566 else
567 # project or wiki doesn't exist
567 # project or wiki doesn't exist
568 all
568 all
569 end
569 end
570 else
570 else
571 all
571 all
572 end
572 end
573 end
573 end
574 end
574 end
575
575
576 # Redmine links
576 # Redmine links
577 #
577 #
578 # Examples:
578 # Examples:
579 # Issues:
579 # Issues:
580 # #52 -> Link to issue #52
580 # #52 -> Link to issue #52
581 # Changesets:
581 # Changesets:
582 # r52 -> Link to revision 52
582 # r52 -> Link to revision 52
583 # commit:a85130f -> Link to scmid starting with a85130f
583 # commit:a85130f -> Link to scmid starting with a85130f
584 # Documents:
584 # Documents:
585 # document#17 -> Link to document with id 17
585 # document#17 -> Link to document with id 17
586 # document:Greetings -> Link to the document with title "Greetings"
586 # document:Greetings -> Link to the document with title "Greetings"
587 # document:"Some document" -> Link to the document with title "Some document"
587 # document:"Some document" -> Link to the document with title "Some document"
588 # Versions:
588 # Versions:
589 # version#3 -> Link to version with id 3
589 # version#3 -> Link to version with id 3
590 # version:1.0.0 -> Link to version named "1.0.0"
590 # version:1.0.0 -> Link to version named "1.0.0"
591 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
591 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
592 # Attachments:
592 # Attachments:
593 # attachment:file.zip -> Link to the attachment of the current object named file.zip
593 # attachment:file.zip -> Link to the attachment of the current object named file.zip
594 # Source files:
594 # Source files:
595 # source:some/file -> Link to the file located at /some/file in the project's repository
595 # source:some/file -> Link to the file located at /some/file in the project's repository
596 # source:some/file@52 -> Link to the file's revision 52
596 # source:some/file@52 -> Link to the file's revision 52
597 # source:some/file#L120 -> Link to line 120 of the file
597 # source:some/file#L120 -> Link to line 120 of the file
598 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
598 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
599 # export:some/file -> Force the download of the file
599 # export:some/file -> Force the download of the file
600 # Forum messages:
600 # Forum messages:
601 # message#1218 -> Link to message with id 1218
601 # message#1218 -> Link to message with id 1218
602 def parse_redmine_links(text, project, obj, attr, only_path, options)
602 def parse_redmine_links(text, project, obj, attr, only_path, options)
603 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
603 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
604 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
604 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
605 link = nil
605 link = nil
606 if esc.nil?
606 if esc.nil?
607 if prefix.nil? && sep == 'r'
607 if prefix.nil? && sep == 'r'
608 if project && (changeset = project.changesets.find_by_revision(identifier))
608 if project && (changeset = project.changesets.find_by_revision(identifier))
609 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
609 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
610 :class => 'changeset',
610 :class => 'changeset',
611 :title => truncate_single_line(changeset.comments, :length => 100))
611 :title => truncate_single_line(changeset.comments, :length => 100))
612 end
612 end
613 elsif sep == '#'
613 elsif sep == '#'
614 oid = identifier.to_i
614 oid = identifier.to_i
615 case prefix
615 case prefix
616 when nil
616 when nil
617 if issue = Issue.visible.find_by_id(oid, :include => :status)
617 if issue = Issue.visible.find_by_id(oid, :include => :status)
618 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
618 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
619 :class => issue.css_classes,
619 :class => issue.css_classes,
620 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
620 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
621 end
621 end
622 when 'document'
622 when 'document'
623 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
623 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
624 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
624 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
625 :class => 'document'
625 :class => 'document'
626 end
626 end
627 when 'version'
627 when 'version'
628 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
628 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
629 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
629 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
630 :class => 'version'
630 :class => 'version'
631 end
631 end
632 when 'message'
632 when 'message'
633 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
633 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
634 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
634 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
635 :controller => 'messages',
635 :controller => 'messages',
636 :action => 'show',
636 :action => 'show',
637 :board_id => message.board,
637 :board_id => message.board,
638 :id => message.root,
638 :id => message.root,
639 :anchor => (message.parent ? "message-#{message.id}" : nil)},
639 :anchor => (message.parent ? "message-#{message.id}" : nil)},
640 :class => 'message'
640 :class => 'message'
641 end
641 end
642 when 'project'
642 when 'project'
643 if p = Project.visible.find_by_id(oid)
643 if p = Project.visible.find_by_id(oid)
644 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
644 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
645 end
645 end
646 end
646 end
647 elsif sep == ':'
647 elsif sep == ':'
648 # removes the double quotes if any
648 # removes the double quotes if any
649 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
649 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
650 case prefix
650 case prefix
651 when 'document'
651 when 'document'
652 if project && document = project.documents.find_by_title(name)
652 if project && document = project.documents.find_by_title(name)
653 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
653 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
654 :class => 'document'
654 :class => 'document'
655 end
655 end
656 when 'version'
656 when 'version'
657 if project && version = project.versions.find_by_name(name)
657 if project && version = project.versions.find_by_name(name)
658 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
658 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
659 :class => 'version'
659 :class => 'version'
660 end
660 end
661 when 'commit'
661 when 'commit'
662 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
662 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
663 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
663 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
664 :class => 'changeset',
664 :class => 'changeset',
665 :title => truncate_single_line(changeset.comments, :length => 100)
665 :title => truncate_single_line(changeset.comments, :length => 100)
666 end
666 end
667 when 'source', 'export'
667 when 'source', 'export'
668 if project && project.repository
668 if project && project.repository
669 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
669 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
670 path, rev, anchor = $1, $3, $5
670 path, rev, anchor = $1, $3, $5
671 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
671 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
672 :path => to_path_param(path),
672 :path => to_path_param(path),
673 :rev => rev,
673 :rev => rev,
674 :anchor => anchor,
674 :anchor => anchor,
675 :format => (prefix == 'export' ? 'raw' : nil)},
675 :format => (prefix == 'export' ? 'raw' : nil)},
676 :class => (prefix == 'export' ? 'source download' : 'source')
676 :class => (prefix == 'export' ? 'source download' : 'source')
677 end
677 end
678 when 'attachment'
678 when 'attachment'
679 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
679 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
680 if attachments && attachment = attachments.detect {|a| a.filename == name }
680 if attachments && attachment = attachments.detect {|a| a.filename == name }
681 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
681 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
682 :class => 'attachment'
682 :class => 'attachment'
683 end
683 end
684 when 'project'
684 when 'project'
685 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
685 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
686 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
686 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
687 end
687 end
688 end
688 end
689 end
689 end
690 end
690 end
691 leading + (link || "#{prefix}#{sep}#{identifier}")
691 leading + (link || "#{prefix}#{sep}#{identifier}")
692 end
692 end
693 end
693 end
694
694
695 # Same as Rails' simple_format helper without using paragraphs
695 # Same as Rails' simple_format helper without using paragraphs
696 def simple_format_without_paragraph(text)
696 def simple_format_without_paragraph(text)
697 text.to_s.
697 text.to_s.
698 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
698 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
699 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
699 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
700 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
700 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
701 end
701 end
702
702
703 def lang_options_for_select(blank=true)
703 def lang_options_for_select(blank=true)
704 (blank ? [["(auto)", ""]] : []) +
704 (blank ? [["(auto)", ""]] : []) +
705 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
705 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
706 end
706 end
707
707
708 def label_tag_for(name, option_tags = nil, options = {})
708 def label_tag_for(name, option_tags = nil, options = {})
709 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
709 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
710 content_tag("label", label_text)
710 content_tag("label", label_text)
711 end
711 end
712
712
713 def labelled_tabular_form_for(name, object, options, &proc)
713 def labelled_tabular_form_for(name, object, options, &proc)
714 options[:html] ||= {}
714 options[:html] ||= {}
715 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
715 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
716 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
716 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
717 end
717 end
718
718
719 def back_url_hidden_field_tag
719 def back_url_hidden_field_tag
720 back_url = params[:back_url] || request.env['HTTP_REFERER']
720 back_url = params[:back_url] || request.env['HTTP_REFERER']
721 back_url = CGI.unescape(back_url.to_s)
721 back_url = CGI.unescape(back_url.to_s)
722 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
722 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
723 end
723 end
724
724
725 def check_all_links(form_name)
725 def check_all_links(form_name)
726 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
726 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
727 " | " +
727 " | " +
728 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
728 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
729 end
729 end
730
730
731 def progress_bar(pcts, options={})
731 def progress_bar(pcts, options={})
732 pcts = [pcts, pcts] unless pcts.is_a?(Array)
732 pcts = [pcts, pcts] unless pcts.is_a?(Array)
733 pcts = pcts.collect(&:round)
733 pcts = pcts.collect(&:round)
734 pcts[1] = pcts[1] - pcts[0]
734 pcts[1] = pcts[1] - pcts[0]
735 pcts << (100 - pcts[1] - pcts[0])
735 pcts << (100 - pcts[1] - pcts[0])
736 width = options[:width] || '100px;'
736 width = options[:width] || '100px;'
737 legend = options[:legend] || ''
737 legend = options[:legend] || ''
738 content_tag('table',
738 content_tag('table',
739 content_tag('tr',
739 content_tag('tr',
740 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
740 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
741 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
741 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
742 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
742 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
743 ), :class => 'progress', :style => "width: #{width};") +
743 ), :class => 'progress', :style => "width: #{width};") +
744 content_tag('p', legend, :class => 'pourcent')
744 content_tag('p', legend, :class => 'pourcent')
745 end
745 end
746
746
747 def checked_image(checked=true)
747 def checked_image(checked=true)
748 if checked
748 if checked
749 image_tag 'toggle_check.png'
749 image_tag 'toggle_check.png'
750 end
750 end
751 end
751 end
752
752
753 def context_menu(url)
753 def context_menu(url)
754 unless @context_menu_included
754 unless @context_menu_included
755 content_for :header_tags do
755 content_for :header_tags do
756 javascript_include_tag('context_menu') +
756 javascript_include_tag('context_menu') +
757 stylesheet_link_tag('context_menu')
757 stylesheet_link_tag('context_menu')
758 end
758 end
759 if l(:direction) == 'rtl'
759 if l(:direction) == 'rtl'
760 content_for :header_tags do
760 content_for :header_tags do
761 stylesheet_link_tag('context_menu_rtl')
761 stylesheet_link_tag('context_menu_rtl')
762 end
762 end
763 end
763 end
764 @context_menu_included = true
764 @context_menu_included = true
765 end
765 end
766 javascript_tag "new ContextMenu('#{ url_for(url) }')"
766 javascript_tag "new ContextMenu('#{ url_for(url) }')"
767 end
767 end
768
768
769 def context_menu_link(name, url, options={})
769 def context_menu_link(name, url, options={})
770 options[:class] ||= ''
770 options[:class] ||= ''
771 if options.delete(:selected)
771 if options.delete(:selected)
772 options[:class] << ' icon-checked disabled'
772 options[:class] << ' icon-checked disabled'
773 options[:disabled] = true
773 options[:disabled] = true
774 end
774 end
775 if options.delete(:disabled)
775 if options.delete(:disabled)
776 options.delete(:method)
776 options.delete(:method)
777 options.delete(:confirm)
777 options.delete(:confirm)
778 options.delete(:onclick)
778 options.delete(:onclick)
779 options[:class] << ' disabled'
779 options[:class] << ' disabled'
780 url = '#'
780 url = '#'
781 end
781 end
782 link_to name, url, options
782 link_to name, url, options
783 end
783 end
784
784
785 def calendar_for(field_id)
785 def calendar_for(field_id)
786 include_calendar_headers_tags
786 include_calendar_headers_tags
787 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
787 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
788 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
788 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
789 end
789 end
790
790
791 def include_calendar_headers_tags
791 def include_calendar_headers_tags
792 unless @calendar_headers_tags_included
792 unless @calendar_headers_tags_included
793 @calendar_headers_tags_included = true
793 @calendar_headers_tags_included = true
794 content_for :header_tags do
794 content_for :header_tags do
795 start_of_week = case Setting.start_of_week.to_i
795 start_of_week = case Setting.start_of_week.to_i
796 when 1
796 when 1
797 'Calendar._FD = 1;' # Monday
797 'Calendar._FD = 1;' # Monday
798 when 7
798 when 7
799 'Calendar._FD = 0;' # Sunday
799 'Calendar._FD = 0;' # Sunday
800 else
800 else
801 '' # use language
801 '' # use language
802 end
802 end
803
803
804 javascript_include_tag('calendar/calendar') +
804 javascript_include_tag('calendar/calendar') +
805 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
805 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
806 javascript_tag(start_of_week) +
806 javascript_tag(start_of_week) +
807 javascript_include_tag('calendar/calendar-setup') +
807 javascript_include_tag('calendar/calendar-setup') +
808 stylesheet_link_tag('calendar')
808 stylesheet_link_tag('calendar')
809 end
809 end
810 end
810 end
811 end
811 end
812
812
813 def content_for(name, content = nil, &block)
813 def content_for(name, content = nil, &block)
814 @has_content ||= {}
814 @has_content ||= {}
815 @has_content[name] = true
815 @has_content[name] = true
816 super(name, content, &block)
816 super(name, content, &block)
817 end
817 end
818
818
819 def has_content?(name)
819 def has_content?(name)
820 (@has_content && @has_content[name]) || false
820 (@has_content && @has_content[name]) || false
821 end
821 end
822
822
823 # Returns the avatar image tag for the given +user+ if avatars are enabled
823 # Returns the avatar image tag for the given +user+ if avatars are enabled
824 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
824 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
825 def avatar(user, options = { })
825 def avatar(user, options = { })
826 if Setting.gravatar_enabled?
826 if Setting.gravatar_enabled?
827 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
827 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
828 email = nil
828 email = nil
829 if user.respond_to?(:mail)
829 if user.respond_to?(:mail)
830 email = user.mail
830 email = user.mail
831 elsif user.to_s =~ %r{<(.+?)>}
831 elsif user.to_s =~ %r{<(.+?)>}
832 email = $1
832 email = $1
833 end
833 end
834 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
834 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
835 end
835 end
836 end
836 end
837
837
838 def favicon
838 def favicon
839 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
839 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
840 end
840 end
841
841
842 private
842 private
843
843
844 def wiki_helper
844 def wiki_helper
845 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
845 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
846 extend helper
846 extend helper
847 return self
847 return self
848 end
848 end
849
849
850 def link_to_remote_content_update(text, url_params)
850 def link_to_remote_content_update(text, url_params)
851 link_to_remote(text,
851 link_to_remote(text,
852 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
852 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
853 {:href => url_for(:params => url_params)}
853 {:href => url_for(:params => url_params)}
854 )
854 )
855 end
855 end
856
856
857 end
857 end
General Comments 0
You need to be logged in to leave comments. Login now