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