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