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