##// END OF EJS Templates
Document project links in ApplicationHelper#parse_redmine_links (#6689)....
Jean-Baptiste Barth -
r11573:1e7c26d9a5ed
parent child
Show More
@@ -1,1248 +1,1251
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) || element.id.to_s == selected
333 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == 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 def option_tag(name, text, value, selected=nil, options={})
351 def option_tag(name, text, value, selected=nil, options={})
352 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
352 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
353 end
353 end
354
354
355 # Truncates and returns the string as a single line
355 # Truncates and returns the string as a single line
356 def truncate_single_line(string, *args)
356 def truncate_single_line(string, *args)
357 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
357 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
358 end
358 end
359
359
360 # Truncates at line break after 250 characters or options[:length]
360 # Truncates at line break after 250 characters or options[:length]
361 def truncate_lines(string, options={})
361 def truncate_lines(string, options={})
362 length = options[:length] || 250
362 length = options[:length] || 250
363 if string.to_s =~ /\A(.{#{length}}.*?)$/m
363 if string.to_s =~ /\A(.{#{length}}.*?)$/m
364 "#{$1}..."
364 "#{$1}..."
365 else
365 else
366 string
366 string
367 end
367 end
368 end
368 end
369
369
370 def anchor(text)
370 def anchor(text)
371 text.to_s.gsub(' ', '_')
371 text.to_s.gsub(' ', '_')
372 end
372 end
373
373
374 def html_hours(text)
374 def html_hours(text)
375 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
375 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
376 end
376 end
377
377
378 def authoring(created, author, options={})
378 def authoring(created, author, options={})
379 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
379 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
380 end
380 end
381
381
382 def time_tag(time)
382 def time_tag(time)
383 text = distance_of_time_in_words(Time.now, time)
383 text = distance_of_time_in_words(Time.now, time)
384 if @project
384 if @project
385 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
385 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
386 else
386 else
387 content_tag('acronym', text, :title => format_time(time))
387 content_tag('acronym', text, :title => format_time(time))
388 end
388 end
389 end
389 end
390
390
391 def syntax_highlight_lines(name, content)
391 def syntax_highlight_lines(name, content)
392 lines = []
392 lines = []
393 syntax_highlight(name, content).each_line { |line| lines << line }
393 syntax_highlight(name, content).each_line { |line| lines << line }
394 lines
394 lines
395 end
395 end
396
396
397 def syntax_highlight(name, content)
397 def syntax_highlight(name, content)
398 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
398 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
399 end
399 end
400
400
401 def to_path_param(path)
401 def to_path_param(path)
402 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
402 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
403 str.blank? ? nil : str
403 str.blank? ? nil : str
404 end
404 end
405
405
406 def reorder_links(name, url, method = :post)
406 def reorder_links(name, url, method = :post)
407 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
407 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
408 url.merge({"#{name}[move_to]" => 'highest'}),
408 url.merge({"#{name}[move_to]" => 'highest'}),
409 :method => method, :title => l(:label_sort_highest)) +
409 :method => method, :title => l(:label_sort_highest)) +
410 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
410 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
411 url.merge({"#{name}[move_to]" => 'higher'}),
411 url.merge({"#{name}[move_to]" => 'higher'}),
412 :method => method, :title => l(:label_sort_higher)) +
412 :method => method, :title => l(:label_sort_higher)) +
413 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
413 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
414 url.merge({"#{name}[move_to]" => 'lower'}),
414 url.merge({"#{name}[move_to]" => 'lower'}),
415 :method => method, :title => l(:label_sort_lower)) +
415 :method => method, :title => l(:label_sort_lower)) +
416 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
416 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
417 url.merge({"#{name}[move_to]" => 'lowest'}),
417 url.merge({"#{name}[move_to]" => 'lowest'}),
418 :method => method, :title => l(:label_sort_lowest))
418 :method => method, :title => l(:label_sort_lowest))
419 end
419 end
420
420
421 def breadcrumb(*args)
421 def breadcrumb(*args)
422 elements = args.flatten
422 elements = args.flatten
423 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
423 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
424 end
424 end
425
425
426 def other_formats_links(&block)
426 def other_formats_links(&block)
427 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
427 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
428 yield Redmine::Views::OtherFormatsBuilder.new(self)
428 yield Redmine::Views::OtherFormatsBuilder.new(self)
429 concat('</p>'.html_safe)
429 concat('</p>'.html_safe)
430 end
430 end
431
431
432 def page_header_title
432 def page_header_title
433 if @project.nil? || @project.new_record?
433 if @project.nil? || @project.new_record?
434 h(Setting.app_title)
434 h(Setting.app_title)
435 else
435 else
436 b = []
436 b = []
437 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
437 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
438 if ancestors.any?
438 if ancestors.any?
439 root = ancestors.shift
439 root = ancestors.shift
440 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
440 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
441 if ancestors.size > 2
441 if ancestors.size > 2
442 b << "\xe2\x80\xa6"
442 b << "\xe2\x80\xa6"
443 ancestors = ancestors[-2, 2]
443 ancestors = ancestors[-2, 2]
444 end
444 end
445 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
445 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
446 end
446 end
447 b << h(@project)
447 b << h(@project)
448 b.join(" \xc2\xbb ").html_safe
448 b.join(" \xc2\xbb ").html_safe
449 end
449 end
450 end
450 end
451
451
452 def html_title(*args)
452 def html_title(*args)
453 if args.empty?
453 if args.empty?
454 title = @html_title || []
454 title = @html_title || []
455 title << @project.name if @project
455 title << @project.name if @project
456 title << Setting.app_title unless Setting.app_title == title.last
456 title << Setting.app_title unless Setting.app_title == title.last
457 title.select {|t| !t.blank? }.join(' - ')
457 title.select {|t| !t.blank? }.join(' - ')
458 else
458 else
459 @html_title ||= []
459 @html_title ||= []
460 @html_title += args
460 @html_title += args
461 end
461 end
462 end
462 end
463
463
464 # Returns the theme, controller name, and action as css classes for the
464 # Returns the theme, controller name, and action as css classes for the
465 # HTML body.
465 # HTML body.
466 def body_css_classes
466 def body_css_classes
467 css = []
467 css = []
468 if theme = Redmine::Themes.theme(Setting.ui_theme)
468 if theme = Redmine::Themes.theme(Setting.ui_theme)
469 css << 'theme-' + theme.name
469 css << 'theme-' + theme.name
470 end
470 end
471
471
472 css << 'controller-' + controller_name
472 css << 'controller-' + controller_name
473 css << 'action-' + action_name
473 css << 'action-' + action_name
474 css.join(' ')
474 css.join(' ')
475 end
475 end
476
476
477 def accesskey(s)
477 def accesskey(s)
478 @used_accesskeys ||= []
478 @used_accesskeys ||= []
479 key = Redmine::AccessKeys.key_for(s)
479 key = Redmine::AccessKeys.key_for(s)
480 return nil if @used_accesskeys.include?(key)
480 return nil if @used_accesskeys.include?(key)
481 @used_accesskeys << key
481 @used_accesskeys << key
482 key
482 key
483 end
483 end
484
484
485 # Formats text according to system settings.
485 # Formats text according to system settings.
486 # 2 ways to call this method:
486 # 2 ways to call this method:
487 # * with a String: textilizable(text, options)
487 # * with a String: textilizable(text, options)
488 # * with an object and one of its attribute: textilizable(issue, :description, options)
488 # * with an object and one of its attribute: textilizable(issue, :description, options)
489 def textilizable(*args)
489 def textilizable(*args)
490 options = args.last.is_a?(Hash) ? args.pop : {}
490 options = args.last.is_a?(Hash) ? args.pop : {}
491 case args.size
491 case args.size
492 when 1
492 when 1
493 obj = options[:object]
493 obj = options[:object]
494 text = args.shift
494 text = args.shift
495 when 2
495 when 2
496 obj = args.shift
496 obj = args.shift
497 attr = args.shift
497 attr = args.shift
498 text = obj.send(attr).to_s
498 text = obj.send(attr).to_s
499 else
499 else
500 raise ArgumentError, 'invalid arguments to textilizable'
500 raise ArgumentError, 'invalid arguments to textilizable'
501 end
501 end
502 return '' if text.blank?
502 return '' if text.blank?
503 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
503 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
504 only_path = options.delete(:only_path) == false ? false : true
504 only_path = options.delete(:only_path) == false ? false : true
505
505
506 text = text.dup
506 text = text.dup
507 macros = catch_macros(text)
507 macros = catch_macros(text)
508 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
508 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
509
509
510 @parsed_headings = []
510 @parsed_headings = []
511 @heading_anchors = {}
511 @heading_anchors = {}
512 @current_section = 0 if options[:edit_section_links]
512 @current_section = 0 if options[:edit_section_links]
513
513
514 parse_sections(text, project, obj, attr, only_path, options)
514 parse_sections(text, project, obj, attr, only_path, options)
515 text = parse_non_pre_blocks(text, obj, macros) do |text|
515 text = parse_non_pre_blocks(text, obj, macros) do |text|
516 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
516 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
517 send method_name, text, project, obj, attr, only_path, options
517 send method_name, text, project, obj, attr, only_path, options
518 end
518 end
519 end
519 end
520 parse_headings(text, project, obj, attr, only_path, options)
520 parse_headings(text, project, obj, attr, only_path, options)
521
521
522 if @parsed_headings.any?
522 if @parsed_headings.any?
523 replace_toc(text, @parsed_headings)
523 replace_toc(text, @parsed_headings)
524 end
524 end
525
525
526 text.html_safe
526 text.html_safe
527 end
527 end
528
528
529 def parse_non_pre_blocks(text, obj, macros)
529 def parse_non_pre_blocks(text, obj, macros)
530 s = StringScanner.new(text)
530 s = StringScanner.new(text)
531 tags = []
531 tags = []
532 parsed = ''
532 parsed = ''
533 while !s.eos?
533 while !s.eos?
534 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
534 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
535 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
535 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
536 if tags.empty?
536 if tags.empty?
537 yield text
537 yield text
538 inject_macros(text, obj, macros) if macros.any?
538 inject_macros(text, obj, macros) if macros.any?
539 else
539 else
540 inject_macros(text, obj, macros, false) if macros.any?
540 inject_macros(text, obj, macros, false) if macros.any?
541 end
541 end
542 parsed << text
542 parsed << text
543 if tag
543 if tag
544 if closing
544 if closing
545 if tags.last == tag.downcase
545 if tags.last == tag.downcase
546 tags.pop
546 tags.pop
547 end
547 end
548 else
548 else
549 tags << tag.downcase
549 tags << tag.downcase
550 end
550 end
551 parsed << full_tag
551 parsed << full_tag
552 end
552 end
553 end
553 end
554 # Close any non closing tags
554 # Close any non closing tags
555 while tag = tags.pop
555 while tag = tags.pop
556 parsed << "</#{tag}>"
556 parsed << "</#{tag}>"
557 end
557 end
558 parsed
558 parsed
559 end
559 end
560
560
561 def parse_inline_attachments(text, project, obj, attr, only_path, options)
561 def parse_inline_attachments(text, project, obj, attr, only_path, options)
562 # when using an image link, try to use an attachment, if possible
562 # when using an image link, try to use an attachment, if possible
563 attachments = options[:attachments] || []
563 attachments = options[:attachments] || []
564 attachments += obj.attachments if obj.respond_to?(:attachments)
564 attachments += obj.attachments if obj.respond_to?(:attachments)
565 if attachments.present?
565 if attachments.present?
566 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
566 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
567 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
567 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
568 # search for the picture in attachments
568 # search for the picture in attachments
569 if found = Attachment.latest_attach(attachments, filename)
569 if found = Attachment.latest_attach(attachments, filename)
570 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
570 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
571 desc = found.description.to_s.gsub('"', '')
571 desc = found.description.to_s.gsub('"', '')
572 if !desc.blank? && alttext.blank?
572 if !desc.blank? && alttext.blank?
573 alt = " title=\"#{desc}\" alt=\"#{desc}\""
573 alt = " title=\"#{desc}\" alt=\"#{desc}\""
574 end
574 end
575 "src=\"#{image_url}\"#{alt}"
575 "src=\"#{image_url}\"#{alt}"
576 else
576 else
577 m
577 m
578 end
578 end
579 end
579 end
580 end
580 end
581 end
581 end
582
582
583 # Wiki links
583 # Wiki links
584 #
584 #
585 # Examples:
585 # Examples:
586 # [[mypage]]
586 # [[mypage]]
587 # [[mypage|mytext]]
587 # [[mypage|mytext]]
588 # wiki links can refer other project wikis, using project name or identifier:
588 # wiki links can refer other project wikis, using project name or identifier:
589 # [[project:]] -> wiki starting page
589 # [[project:]] -> wiki starting page
590 # [[project:|mytext]]
590 # [[project:|mytext]]
591 # [[project:mypage]]
591 # [[project:mypage]]
592 # [[project:mypage|mytext]]
592 # [[project:mypage|mytext]]
593 def parse_wiki_links(text, project, obj, attr, only_path, options)
593 def parse_wiki_links(text, project, obj, attr, only_path, options)
594 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
594 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
595 link_project = project
595 link_project = project
596 esc, all, page, title = $1, $2, $3, $5
596 esc, all, page, title = $1, $2, $3, $5
597 if esc.nil?
597 if esc.nil?
598 if page =~ /^([^\:]+)\:(.*)$/
598 if page =~ /^([^\:]+)\:(.*)$/
599 identifier, page = $1, $2
599 identifier, page = $1, $2
600 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
600 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
601 title ||= identifier if page.blank?
601 title ||= identifier if page.blank?
602 end
602 end
603
603
604 if link_project && link_project.wiki
604 if link_project && link_project.wiki
605 # extract anchor
605 # extract anchor
606 anchor = nil
606 anchor = nil
607 if page =~ /^(.+?)\#(.+)$/
607 if page =~ /^(.+?)\#(.+)$/
608 page, anchor = $1, $2
608 page, anchor = $1, $2
609 end
609 end
610 anchor = sanitize_anchor_name(anchor) if anchor.present?
610 anchor = sanitize_anchor_name(anchor) if anchor.present?
611 # check if page exists
611 # check if page exists
612 wiki_page = link_project.wiki.find_page(page)
612 wiki_page = link_project.wiki.find_page(page)
613 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
613 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
614 "##{anchor}"
614 "##{anchor}"
615 else
615 else
616 case options[:wiki_links]
616 case options[:wiki_links]
617 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
617 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
618 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
618 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
619 else
619 else
620 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
620 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
621 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
621 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
622 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
622 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
623 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
623 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
624 end
624 end
625 end
625 end
626 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
626 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
627 else
627 else
628 # project or wiki doesn't exist
628 # project or wiki doesn't exist
629 all
629 all
630 end
630 end
631 else
631 else
632 all
632 all
633 end
633 end
634 end
634 end
635 end
635 end
636
636
637 # Redmine links
637 # Redmine links
638 #
638 #
639 # Examples:
639 # Examples:
640 # Issues:
640 # Issues:
641 # #52 -> Link to issue #52
641 # #52 -> Link to issue #52
642 # Changesets:
642 # Changesets:
643 # r52 -> Link to revision 52
643 # r52 -> Link to revision 52
644 # commit:a85130f -> Link to scmid starting with a85130f
644 # commit:a85130f -> Link to scmid starting with a85130f
645 # Documents:
645 # Documents:
646 # document#17 -> Link to document with id 17
646 # document#17 -> Link to document with id 17
647 # document:Greetings -> Link to the document with title "Greetings"
647 # document:Greetings -> Link to the document with title "Greetings"
648 # document:"Some document" -> Link to the document with title "Some document"
648 # document:"Some document" -> Link to the document with title "Some document"
649 # Versions:
649 # Versions:
650 # version#3 -> Link to version with id 3
650 # version#3 -> Link to version with id 3
651 # version:1.0.0 -> Link to version named "1.0.0"
651 # version:1.0.0 -> Link to version named "1.0.0"
652 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
652 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
653 # Attachments:
653 # Attachments:
654 # attachment:file.zip -> Link to the attachment of the current object named file.zip
654 # attachment:file.zip -> Link to the attachment of the current object named file.zip
655 # Source files:
655 # Source files:
656 # source:some/file -> Link to the file located at /some/file in the project's repository
656 # source:some/file -> Link to the file located at /some/file in the project's repository
657 # source:some/file@52 -> Link to the file's revision 52
657 # source:some/file@52 -> Link to the file's revision 52
658 # source:some/file#L120 -> Link to line 120 of the file
658 # source:some/file#L120 -> Link to line 120 of the file
659 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
659 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
660 # export:some/file -> Force the download of the file
660 # export:some/file -> Force the download of the file
661 # Forum messages:
661 # Forum messages:
662 # message#1218 -> Link to message with id 1218
662 # message#1218 -> Link to message with id 1218
663 # Projects:
664 # project:someproject -> Link to project named "someproject"
665 # project#3 -> Link to project with id 3
663 #
666 #
664 # Links can refer other objects from other projects, using project identifier:
667 # Links can refer other objects from other projects, using project identifier:
665 # identifier:r52
668 # identifier:r52
666 # identifier:document:"Some document"
669 # identifier:document:"Some document"
667 # identifier:version:1.0.0
670 # identifier:version:1.0.0
668 # identifier:source:some/file
671 # identifier:source:some/file
669 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
672 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
670 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
673 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
671 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
674 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
672 link = nil
675 link = nil
673 project = default_project
676 project = default_project
674 if project_identifier
677 if project_identifier
675 project = Project.visible.find_by_identifier(project_identifier)
678 project = Project.visible.find_by_identifier(project_identifier)
676 end
679 end
677 if esc.nil?
680 if esc.nil?
678 if prefix.nil? && sep == 'r'
681 if prefix.nil? && sep == 'r'
679 if project
682 if project
680 repository = nil
683 repository = nil
681 if repo_identifier
684 if repo_identifier
682 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
685 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
683 else
686 else
684 repository = project.repository
687 repository = project.repository
685 end
688 end
686 # project.changesets.visible raises an SQL error because of a double join on repositories
689 # project.changesets.visible raises an SQL error because of a double join on repositories
687 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
690 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
688 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
691 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},
689 :class => 'changeset',
692 :class => 'changeset',
690 :title => truncate_single_line(changeset.comments, :length => 100))
693 :title => truncate_single_line(changeset.comments, :length => 100))
691 end
694 end
692 end
695 end
693 elsif sep == '#'
696 elsif sep == '#'
694 oid = identifier.to_i
697 oid = identifier.to_i
695 case prefix
698 case prefix
696 when nil
699 when nil
697 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
700 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
698 anchor = comment_id ? "note-#{comment_id}" : nil
701 anchor = comment_id ? "note-#{comment_id}" : nil
699 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
702 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
700 :class => issue.css_classes,
703 :class => issue.css_classes,
701 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
704 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
702 end
705 end
703 when 'document'
706 when 'document'
704 if document = Document.visible.find_by_id(oid)
707 if document = Document.visible.find_by_id(oid)
705 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
708 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
706 :class => 'document'
709 :class => 'document'
707 end
710 end
708 when 'version'
711 when 'version'
709 if version = Version.visible.find_by_id(oid)
712 if version = Version.visible.find_by_id(oid)
710 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
713 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
711 :class => 'version'
714 :class => 'version'
712 end
715 end
713 when 'message'
716 when 'message'
714 if message = Message.visible.find_by_id(oid, :include => :parent)
717 if message = Message.visible.find_by_id(oid, :include => :parent)
715 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
718 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
716 end
719 end
717 when 'forum'
720 when 'forum'
718 if board = Board.visible.find_by_id(oid)
721 if board = Board.visible.find_by_id(oid)
719 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
722 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
720 :class => 'board'
723 :class => 'board'
721 end
724 end
722 when 'news'
725 when 'news'
723 if news = News.visible.find_by_id(oid)
726 if news = News.visible.find_by_id(oid)
724 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
727 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
725 :class => 'news'
728 :class => 'news'
726 end
729 end
727 when 'project'
730 when 'project'
728 if p = Project.visible.find_by_id(oid)
731 if p = Project.visible.find_by_id(oid)
729 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
732 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
730 end
733 end
731 end
734 end
732 elsif sep == ':'
735 elsif sep == ':'
733 # removes the double quotes if any
736 # removes the double quotes if any
734 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
737 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
735 case prefix
738 case prefix
736 when 'document'
739 when 'document'
737 if project && document = project.documents.visible.find_by_title(name)
740 if project && document = project.documents.visible.find_by_title(name)
738 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
741 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
739 :class => 'document'
742 :class => 'document'
740 end
743 end
741 when 'version'
744 when 'version'
742 if project && version = project.versions.visible.find_by_name(name)
745 if project && version = project.versions.visible.find_by_name(name)
743 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
746 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
744 :class => 'version'
747 :class => 'version'
745 end
748 end
746 when 'forum'
749 when 'forum'
747 if project && board = project.boards.visible.find_by_name(name)
750 if project && board = project.boards.visible.find_by_name(name)
748 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
751 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
749 :class => 'board'
752 :class => 'board'
750 end
753 end
751 when 'news'
754 when 'news'
752 if project && news = project.news.visible.find_by_title(name)
755 if project && news = project.news.visible.find_by_title(name)
753 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
756 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
754 :class => 'news'
757 :class => 'news'
755 end
758 end
756 when 'commit', 'source', 'export'
759 when 'commit', 'source', 'export'
757 if project
760 if project
758 repository = nil
761 repository = nil
759 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
762 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
760 repo_prefix, repo_identifier, name = $1, $2, $3
763 repo_prefix, repo_identifier, name = $1, $2, $3
761 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
764 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
762 else
765 else
763 repository = project.repository
766 repository = project.repository
764 end
767 end
765 if prefix == 'commit'
768 if prefix == 'commit'
766 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
769 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
767 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
770 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},
768 :class => 'changeset',
771 :class => 'changeset',
769 :title => truncate_single_line(changeset.comments, :length => 100)
772 :title => truncate_single_line(changeset.comments, :length => 100)
770 end
773 end
771 else
774 else
772 if repository && User.current.allowed_to?(:browse_repository, project)
775 if repository && User.current.allowed_to?(:browse_repository, project)
773 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
776 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
774 path, rev, anchor = $1, $3, $5
777 path, rev, anchor = $1, $3, $5
775 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
778 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
776 :path => to_path_param(path),
779 :path => to_path_param(path),
777 :rev => rev,
780 :rev => rev,
778 :anchor => anchor},
781 :anchor => anchor},
779 :class => (prefix == 'export' ? 'source download' : 'source')
782 :class => (prefix == 'export' ? 'source download' : 'source')
780 end
783 end
781 end
784 end
782 repo_prefix = nil
785 repo_prefix = nil
783 end
786 end
784 when 'attachment'
787 when 'attachment'
785 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
788 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
786 if attachments && attachment = Attachment.latest_attach(attachments, name)
789 if attachments && attachment = Attachment.latest_attach(attachments, name)
787 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
790 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
788 end
791 end
789 when 'project'
792 when 'project'
790 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
793 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
791 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
794 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
792 end
795 end
793 end
796 end
794 end
797 end
795 end
798 end
796 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
799 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
797 end
800 end
798 end
801 end
799
802
800 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
803 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
801
804
802 def parse_sections(text, project, obj, attr, only_path, options)
805 def parse_sections(text, project, obj, attr, only_path, options)
803 return unless options[:edit_section_links]
806 return unless options[:edit_section_links]
804 text.gsub!(HEADING_RE) do
807 text.gsub!(HEADING_RE) do
805 heading = $1
808 heading = $1
806 @current_section += 1
809 @current_section += 1
807 if @current_section > 1
810 if @current_section > 1
808 content_tag('div',
811 content_tag('div',
809 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
812 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
810 :class => 'contextual',
813 :class => 'contextual',
811 :title => l(:button_edit_section)) + heading.html_safe
814 :title => l(:button_edit_section)) + heading.html_safe
812 else
815 else
813 heading
816 heading
814 end
817 end
815 end
818 end
816 end
819 end
817
820
818 # Headings and TOC
821 # Headings and TOC
819 # Adds ids and links to headings unless options[:headings] is set to false
822 # Adds ids and links to headings unless options[:headings] is set to false
820 def parse_headings(text, project, obj, attr, only_path, options)
823 def parse_headings(text, project, obj, attr, only_path, options)
821 return if options[:headings] == false
824 return if options[:headings] == false
822
825
823 text.gsub!(HEADING_RE) do
826 text.gsub!(HEADING_RE) do
824 level, attrs, content = $2.to_i, $3, $4
827 level, attrs, content = $2.to_i, $3, $4
825 item = strip_tags(content).strip
828 item = strip_tags(content).strip
826 anchor = sanitize_anchor_name(item)
829 anchor = sanitize_anchor_name(item)
827 # used for single-file wiki export
830 # used for single-file wiki export
828 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
831 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
829 @heading_anchors[anchor] ||= 0
832 @heading_anchors[anchor] ||= 0
830 idx = (@heading_anchors[anchor] += 1)
833 idx = (@heading_anchors[anchor] += 1)
831 if idx > 1
834 if idx > 1
832 anchor = "#{anchor}-#{idx}"
835 anchor = "#{anchor}-#{idx}"
833 end
836 end
834 @parsed_headings << [level, anchor, item]
837 @parsed_headings << [level, anchor, item]
835 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
838 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
836 end
839 end
837 end
840 end
838
841
839 MACROS_RE = /(
842 MACROS_RE = /(
840 (!)? # escaping
843 (!)? # escaping
841 (
844 (
842 \{\{ # opening tag
845 \{\{ # opening tag
843 ([\w]+) # macro name
846 ([\w]+) # macro name
844 (\(([^\n\r]*?)\))? # optional arguments
847 (\(([^\n\r]*?)\))? # optional arguments
845 ([\n\r].*?[\n\r])? # optional block of text
848 ([\n\r].*?[\n\r])? # optional block of text
846 \}\} # closing tag
849 \}\} # closing tag
847 )
850 )
848 )/mx unless const_defined?(:MACROS_RE)
851 )/mx unless const_defined?(:MACROS_RE)
849
852
850 MACRO_SUB_RE = /(
853 MACRO_SUB_RE = /(
851 \{\{
854 \{\{
852 macro\((\d+)\)
855 macro\((\d+)\)
853 \}\}
856 \}\}
854 )/x unless const_defined?(:MACRO_SUB_RE)
857 )/x unless const_defined?(:MACRO_SUB_RE)
855
858
856 # Extracts macros from text
859 # Extracts macros from text
857 def catch_macros(text)
860 def catch_macros(text)
858 macros = {}
861 macros = {}
859 text.gsub!(MACROS_RE) do
862 text.gsub!(MACROS_RE) do
860 all, macro = $1, $4.downcase
863 all, macro = $1, $4.downcase
861 if macro_exists?(macro) || all =~ MACRO_SUB_RE
864 if macro_exists?(macro) || all =~ MACRO_SUB_RE
862 index = macros.size
865 index = macros.size
863 macros[index] = all
866 macros[index] = all
864 "{{macro(#{index})}}"
867 "{{macro(#{index})}}"
865 else
868 else
866 all
869 all
867 end
870 end
868 end
871 end
869 macros
872 macros
870 end
873 end
871
874
872 # Executes and replaces macros in text
875 # Executes and replaces macros in text
873 def inject_macros(text, obj, macros, execute=true)
876 def inject_macros(text, obj, macros, execute=true)
874 text.gsub!(MACRO_SUB_RE) do
877 text.gsub!(MACRO_SUB_RE) do
875 all, index = $1, $2.to_i
878 all, index = $1, $2.to_i
876 orig = macros.delete(index)
879 orig = macros.delete(index)
877 if execute && orig && orig =~ MACROS_RE
880 if execute && orig && orig =~ MACROS_RE
878 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
881 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
879 if esc.nil?
882 if esc.nil?
880 h(exec_macro(macro, obj, args, block) || all)
883 h(exec_macro(macro, obj, args, block) || all)
881 else
884 else
882 h(all)
885 h(all)
883 end
886 end
884 elsif orig
887 elsif orig
885 h(orig)
888 h(orig)
886 else
889 else
887 h(all)
890 h(all)
888 end
891 end
889 end
892 end
890 end
893 end
891
894
892 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
895 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
893
896
894 # Renders the TOC with given headings
897 # Renders the TOC with given headings
895 def replace_toc(text, headings)
898 def replace_toc(text, headings)
896 text.gsub!(TOC_RE) do
899 text.gsub!(TOC_RE) do
897 # Keep only the 4 first levels
900 # Keep only the 4 first levels
898 headings = headings.select{|level, anchor, item| level <= 4}
901 headings = headings.select{|level, anchor, item| level <= 4}
899 if headings.empty?
902 if headings.empty?
900 ''
903 ''
901 else
904 else
902 div_class = 'toc'
905 div_class = 'toc'
903 div_class << ' right' if $1 == '>'
906 div_class << ' right' if $1 == '>'
904 div_class << ' left' if $1 == '<'
907 div_class << ' left' if $1 == '<'
905 out = "<ul class=\"#{div_class}\"><li>"
908 out = "<ul class=\"#{div_class}\"><li>"
906 root = headings.map(&:first).min
909 root = headings.map(&:first).min
907 current = root
910 current = root
908 started = false
911 started = false
909 headings.each do |level, anchor, item|
912 headings.each do |level, anchor, item|
910 if level > current
913 if level > current
911 out << '<ul><li>' * (level - current)
914 out << '<ul><li>' * (level - current)
912 elsif level < current
915 elsif level < current
913 out << "</li></ul>\n" * (current - level) + "</li><li>"
916 out << "</li></ul>\n" * (current - level) + "</li><li>"
914 elsif started
917 elsif started
915 out << '</li><li>'
918 out << '</li><li>'
916 end
919 end
917 out << "<a href=\"##{anchor}\">#{item}</a>"
920 out << "<a href=\"##{anchor}\">#{item}</a>"
918 current = level
921 current = level
919 started = true
922 started = true
920 end
923 end
921 out << '</li></ul>' * (current - root)
924 out << '</li></ul>' * (current - root)
922 out << '</li></ul>'
925 out << '</li></ul>'
923 end
926 end
924 end
927 end
925 end
928 end
926
929
927 # Same as Rails' simple_format helper without using paragraphs
930 # Same as Rails' simple_format helper without using paragraphs
928 def simple_format_without_paragraph(text)
931 def simple_format_without_paragraph(text)
929 text.to_s.
932 text.to_s.
930 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
933 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
931 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
934 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
932 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
935 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
933 html_safe
936 html_safe
934 end
937 end
935
938
936 def lang_options_for_select(blank=true)
939 def lang_options_for_select(blank=true)
937 (blank ? [["(auto)", ""]] : []) + languages_options
940 (blank ? [["(auto)", ""]] : []) + languages_options
938 end
941 end
939
942
940 def label_tag_for(name, option_tags = nil, options = {})
943 def label_tag_for(name, option_tags = nil, options = {})
941 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
944 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
942 content_tag("label", label_text)
945 content_tag("label", label_text)
943 end
946 end
944
947
945 def labelled_form_for(*args, &proc)
948 def labelled_form_for(*args, &proc)
946 args << {} unless args.last.is_a?(Hash)
949 args << {} unless args.last.is_a?(Hash)
947 options = args.last
950 options = args.last
948 if args.first.is_a?(Symbol)
951 if args.first.is_a?(Symbol)
949 options.merge!(:as => args.shift)
952 options.merge!(:as => args.shift)
950 end
953 end
951 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
954 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
952 form_for(*args, &proc)
955 form_for(*args, &proc)
953 end
956 end
954
957
955 def labelled_fields_for(*args, &proc)
958 def labelled_fields_for(*args, &proc)
956 args << {} unless args.last.is_a?(Hash)
959 args << {} unless args.last.is_a?(Hash)
957 options = args.last
960 options = args.last
958 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
961 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
959 fields_for(*args, &proc)
962 fields_for(*args, &proc)
960 end
963 end
961
964
962 def labelled_remote_form_for(*args, &proc)
965 def labelled_remote_form_for(*args, &proc)
963 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
966 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
964 args << {} unless args.last.is_a?(Hash)
967 args << {} unless args.last.is_a?(Hash)
965 options = args.last
968 options = args.last
966 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
969 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
967 form_for(*args, &proc)
970 form_for(*args, &proc)
968 end
971 end
969
972
970 def error_messages_for(*objects)
973 def error_messages_for(*objects)
971 html = ""
974 html = ""
972 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
975 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
973 errors = objects.map {|o| o.errors.full_messages}.flatten
976 errors = objects.map {|o| o.errors.full_messages}.flatten
974 if errors.any?
977 if errors.any?
975 html << "<div id='errorExplanation'><ul>\n"
978 html << "<div id='errorExplanation'><ul>\n"
976 errors.each do |error|
979 errors.each do |error|
977 html << "<li>#{h error}</li>\n"
980 html << "<li>#{h error}</li>\n"
978 end
981 end
979 html << "</ul></div>\n"
982 html << "</ul></div>\n"
980 end
983 end
981 html.html_safe
984 html.html_safe
982 end
985 end
983
986
984 def delete_link(url, options={})
987 def delete_link(url, options={})
985 options = {
988 options = {
986 :method => :delete,
989 :method => :delete,
987 :data => {:confirm => l(:text_are_you_sure)},
990 :data => {:confirm => l(:text_are_you_sure)},
988 :class => 'icon icon-del'
991 :class => 'icon icon-del'
989 }.merge(options)
992 }.merge(options)
990
993
991 link_to l(:button_delete), url, options
994 link_to l(:button_delete), url, options
992 end
995 end
993
996
994 def preview_link(url, form, target='preview', options={})
997 def preview_link(url, form, target='preview', options={})
995 content_tag 'a', l(:label_preview), {
998 content_tag 'a', l(:label_preview), {
996 :href => "#",
999 :href => "#",
997 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1000 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
998 :accesskey => accesskey(:preview)
1001 :accesskey => accesskey(:preview)
999 }.merge(options)
1002 }.merge(options)
1000 end
1003 end
1001
1004
1002 def link_to_function(name, function, html_options={})
1005 def link_to_function(name, function, html_options={})
1003 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1006 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1004 end
1007 end
1005
1008
1006 # Helper to render JSON in views
1009 # Helper to render JSON in views
1007 def raw_json(arg)
1010 def raw_json(arg)
1008 arg.to_json.to_s.gsub('/', '\/').html_safe
1011 arg.to_json.to_s.gsub('/', '\/').html_safe
1009 end
1012 end
1010
1013
1011 def back_url
1014 def back_url
1012 url = params[:back_url]
1015 url = params[:back_url]
1013 if url.nil? && referer = request.env['HTTP_REFERER']
1016 if url.nil? && referer = request.env['HTTP_REFERER']
1014 url = CGI.unescape(referer.to_s)
1017 url = CGI.unescape(referer.to_s)
1015 end
1018 end
1016 url
1019 url
1017 end
1020 end
1018
1021
1019 def back_url_hidden_field_tag
1022 def back_url_hidden_field_tag
1020 url = back_url
1023 url = back_url
1021 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1024 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1022 end
1025 end
1023
1026
1024 def check_all_links(form_name)
1027 def check_all_links(form_name)
1025 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1028 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1026 " | ".html_safe +
1029 " | ".html_safe +
1027 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1030 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1028 end
1031 end
1029
1032
1030 def progress_bar(pcts, options={})
1033 def progress_bar(pcts, options={})
1031 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1034 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1032 pcts = pcts.collect(&:round)
1035 pcts = pcts.collect(&:round)
1033 pcts[1] = pcts[1] - pcts[0]
1036 pcts[1] = pcts[1] - pcts[0]
1034 pcts << (100 - pcts[1] - pcts[0])
1037 pcts << (100 - pcts[1] - pcts[0])
1035 width = options[:width] || '100px;'
1038 width = options[:width] || '100px;'
1036 legend = options[:legend] || ''
1039 legend = options[:legend] || ''
1037 content_tag('table',
1040 content_tag('table',
1038 content_tag('tr',
1041 content_tag('tr',
1039 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1042 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1040 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1043 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1041 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1044 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1042 ), :class => 'progress', :style => "width: #{width};").html_safe +
1045 ), :class => 'progress', :style => "width: #{width};").html_safe +
1043 content_tag('p', legend, :class => 'percent').html_safe
1046 content_tag('p', legend, :class => 'percent').html_safe
1044 end
1047 end
1045
1048
1046 def checked_image(checked=true)
1049 def checked_image(checked=true)
1047 if checked
1050 if checked
1048 image_tag 'toggle_check.png'
1051 image_tag 'toggle_check.png'
1049 end
1052 end
1050 end
1053 end
1051
1054
1052 def context_menu(url)
1055 def context_menu(url)
1053 unless @context_menu_included
1056 unless @context_menu_included
1054 content_for :header_tags do
1057 content_for :header_tags do
1055 javascript_include_tag('context_menu') +
1058 javascript_include_tag('context_menu') +
1056 stylesheet_link_tag('context_menu')
1059 stylesheet_link_tag('context_menu')
1057 end
1060 end
1058 if l(:direction) == 'rtl'
1061 if l(:direction) == 'rtl'
1059 content_for :header_tags do
1062 content_for :header_tags do
1060 stylesheet_link_tag('context_menu_rtl')
1063 stylesheet_link_tag('context_menu_rtl')
1061 end
1064 end
1062 end
1065 end
1063 @context_menu_included = true
1066 @context_menu_included = true
1064 end
1067 end
1065 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1068 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1066 end
1069 end
1067
1070
1068 def calendar_for(field_id)
1071 def calendar_for(field_id)
1069 include_calendar_headers_tags
1072 include_calendar_headers_tags
1070 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1073 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1071 end
1074 end
1072
1075
1073 def include_calendar_headers_tags
1076 def include_calendar_headers_tags
1074 unless @calendar_headers_tags_included
1077 unless @calendar_headers_tags_included
1075 @calendar_headers_tags_included = true
1078 @calendar_headers_tags_included = true
1076 content_for :header_tags do
1079 content_for :header_tags do
1077 start_of_week = Setting.start_of_week
1080 start_of_week = Setting.start_of_week
1078 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1081 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1079 # Redmine uses 1..7 (monday..sunday) in settings and locales
1082 # Redmine uses 1..7 (monday..sunday) in settings and locales
1080 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1083 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1081 start_of_week = start_of_week.to_i % 7
1084 start_of_week = start_of_week.to_i % 7
1082
1085
1083 tags = javascript_tag(
1086 tags = javascript_tag(
1084 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1087 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1085 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1088 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1086 path_to_image('/images/calendar.png') +
1089 path_to_image('/images/calendar.png') +
1087 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: true, changeMonth: true, changeYear: true};")
1090 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: true, changeMonth: true, changeYear: true};")
1088 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1091 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1089 unless jquery_locale == 'en'
1092 unless jquery_locale == 'en'
1090 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1093 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1091 end
1094 end
1092 tags
1095 tags
1093 end
1096 end
1094 end
1097 end
1095 end
1098 end
1096
1099
1097 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1100 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1098 # Examples:
1101 # Examples:
1099 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1102 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1100 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1103 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1101 #
1104 #
1102 def stylesheet_link_tag(*sources)
1105 def stylesheet_link_tag(*sources)
1103 options = sources.last.is_a?(Hash) ? sources.pop : {}
1106 options = sources.last.is_a?(Hash) ? sources.pop : {}
1104 plugin = options.delete(:plugin)
1107 plugin = options.delete(:plugin)
1105 sources = sources.map do |source|
1108 sources = sources.map do |source|
1106 if plugin
1109 if plugin
1107 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1110 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1108 elsif current_theme && current_theme.stylesheets.include?(source)
1111 elsif current_theme && current_theme.stylesheets.include?(source)
1109 current_theme.stylesheet_path(source)
1112 current_theme.stylesheet_path(source)
1110 else
1113 else
1111 source
1114 source
1112 end
1115 end
1113 end
1116 end
1114 super sources, options
1117 super sources, options
1115 end
1118 end
1116
1119
1117 # Overrides Rails' image_tag with themes and plugins support.
1120 # Overrides Rails' image_tag with themes and plugins support.
1118 # Examples:
1121 # Examples:
1119 # image_tag('image.png') # => picks image.png from the current theme or defaults
1122 # image_tag('image.png') # => picks image.png from the current theme or defaults
1120 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1123 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1121 #
1124 #
1122 def image_tag(source, options={})
1125 def image_tag(source, options={})
1123 if plugin = options.delete(:plugin)
1126 if plugin = options.delete(:plugin)
1124 source = "/plugin_assets/#{plugin}/images/#{source}"
1127 source = "/plugin_assets/#{plugin}/images/#{source}"
1125 elsif current_theme && current_theme.images.include?(source)
1128 elsif current_theme && current_theme.images.include?(source)
1126 source = current_theme.image_path(source)
1129 source = current_theme.image_path(source)
1127 end
1130 end
1128 super source, options
1131 super source, options
1129 end
1132 end
1130
1133
1131 # Overrides Rails' javascript_include_tag with plugins support
1134 # Overrides Rails' javascript_include_tag with plugins support
1132 # Examples:
1135 # Examples:
1133 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1136 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1134 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1137 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1135 #
1138 #
1136 def javascript_include_tag(*sources)
1139 def javascript_include_tag(*sources)
1137 options = sources.last.is_a?(Hash) ? sources.pop : {}
1140 options = sources.last.is_a?(Hash) ? sources.pop : {}
1138 if plugin = options.delete(:plugin)
1141 if plugin = options.delete(:plugin)
1139 sources = sources.map do |source|
1142 sources = sources.map do |source|
1140 if plugin
1143 if plugin
1141 "/plugin_assets/#{plugin}/javascripts/#{source}"
1144 "/plugin_assets/#{plugin}/javascripts/#{source}"
1142 else
1145 else
1143 source
1146 source
1144 end
1147 end
1145 end
1148 end
1146 end
1149 end
1147 super sources, options
1150 super sources, options
1148 end
1151 end
1149
1152
1150 def content_for(name, content = nil, &block)
1153 def content_for(name, content = nil, &block)
1151 @has_content ||= {}
1154 @has_content ||= {}
1152 @has_content[name] = true
1155 @has_content[name] = true
1153 super(name, content, &block)
1156 super(name, content, &block)
1154 end
1157 end
1155
1158
1156 def has_content?(name)
1159 def has_content?(name)
1157 (@has_content && @has_content[name]) || false
1160 (@has_content && @has_content[name]) || false
1158 end
1161 end
1159
1162
1160 def sidebar_content?
1163 def sidebar_content?
1161 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1164 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1162 end
1165 end
1163
1166
1164 def view_layouts_base_sidebar_hook_response
1167 def view_layouts_base_sidebar_hook_response
1165 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1168 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1166 end
1169 end
1167
1170
1168 def email_delivery_enabled?
1171 def email_delivery_enabled?
1169 !!ActionMailer::Base.perform_deliveries
1172 !!ActionMailer::Base.perform_deliveries
1170 end
1173 end
1171
1174
1172 # Returns the avatar image tag for the given +user+ if avatars are enabled
1175 # Returns the avatar image tag for the given +user+ if avatars are enabled
1173 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1176 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1174 def avatar(user, options = { })
1177 def avatar(user, options = { })
1175 if Setting.gravatar_enabled?
1178 if Setting.gravatar_enabled?
1176 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1179 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1177 email = nil
1180 email = nil
1178 if user.respond_to?(:mail)
1181 if user.respond_to?(:mail)
1179 email = user.mail
1182 email = user.mail
1180 elsif user.to_s =~ %r{<(.+?)>}
1183 elsif user.to_s =~ %r{<(.+?)>}
1181 email = $1
1184 email = $1
1182 end
1185 end
1183 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1186 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1184 else
1187 else
1185 ''
1188 ''
1186 end
1189 end
1187 end
1190 end
1188
1191
1189 def sanitize_anchor_name(anchor)
1192 def sanitize_anchor_name(anchor)
1190 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1193 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1191 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1194 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1192 else
1195 else
1193 # TODO: remove when ruby1.8 is no longer supported
1196 # TODO: remove when ruby1.8 is no longer supported
1194 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1197 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1195 end
1198 end
1196 end
1199 end
1197
1200
1198 # Returns the javascript tags that are included in the html layout head
1201 # Returns the javascript tags that are included in the html layout head
1199 def javascript_heads
1202 def javascript_heads
1200 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1203 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1201 unless User.current.pref.warn_on_leaving_unsaved == '0'
1204 unless User.current.pref.warn_on_leaving_unsaved == '0'
1202 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1205 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1203 end
1206 end
1204 tags
1207 tags
1205 end
1208 end
1206
1209
1207 def favicon
1210 def favicon
1208 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1211 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1209 end
1212 end
1210
1213
1211 def robot_exclusion_tag
1214 def robot_exclusion_tag
1212 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1215 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1213 end
1216 end
1214
1217
1215 # Returns true if arg is expected in the API response
1218 # Returns true if arg is expected in the API response
1216 def include_in_api_response?(arg)
1219 def include_in_api_response?(arg)
1217 unless @included_in_api_response
1220 unless @included_in_api_response
1218 param = params[:include]
1221 param = params[:include]
1219 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1222 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1220 @included_in_api_response.collect!(&:strip)
1223 @included_in_api_response.collect!(&:strip)
1221 end
1224 end
1222 @included_in_api_response.include?(arg.to_s)
1225 @included_in_api_response.include?(arg.to_s)
1223 end
1226 end
1224
1227
1225 # Returns options or nil if nometa param or X-Redmine-Nometa header
1228 # Returns options or nil if nometa param or X-Redmine-Nometa header
1226 # was set in the request
1229 # was set in the request
1227 def api_meta(options)
1230 def api_meta(options)
1228 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1231 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1229 # compatibility mode for activeresource clients that raise
1232 # compatibility mode for activeresource clients that raise
1230 # an error when unserializing an array with attributes
1233 # an error when unserializing an array with attributes
1231 nil
1234 nil
1232 else
1235 else
1233 options
1236 options
1234 end
1237 end
1235 end
1238 end
1236
1239
1237 private
1240 private
1238
1241
1239 def wiki_helper
1242 def wiki_helper
1240 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1243 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1241 extend helper
1244 extend helper
1242 return self
1245 return self
1243 end
1246 end
1244
1247
1245 def link_to_content_update(text, url_params = {}, html_options = {})
1248 def link_to_content_update(text, url_params = {}, html_options = {})
1246 link_to(text, url_params, html_options)
1249 link_to(text, url_params, html_options)
1247 end
1250 end
1248 end
1251 end
General Comments 0
You need to be logged in to leave comments. Login now