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