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