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