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