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