comparison app/helpers/.svn/text-base/application_helper.rb.svn-base @ 523:0b6c82dead28 luisf

Merge from branch "cannam"
author luisf <luis.figueira@eecs.qmul.ac.uk>
date Mon, 25 Jul 2011 14:23:37 +0100
parents 753f1380d6bc
children
comparison
equal deleted inserted replaced
318:f7c525dc7585 523:0b6c82dead28
1 # Redmine - project management software 1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 # 3 #
4 # This program is free software; you can redistribute it and/or 4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License 5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2 6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version. 7 # of the License, or (at your option) any later version.
61 end 61 end
62 end 62 end
63 63
64 # Displays a link to +issue+ with its subject. 64 # Displays a link to +issue+ with its subject.
65 # Examples: 65 # Examples:
66 # 66 #
67 # link_to_issue(issue) # => Defect #6: This is the subject 67 # link_to_issue(issue) # => Defect #6: This is the subject
68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... 68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
69 # link_to_issue(issue, :subject => false) # => Defect #6 69 # link_to_issue(issue, :subject => false) # => Defect #6
70 # link_to_issue(issue, :project => true) # => Foo - Defect #6 70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
71 # 71 #
78 subject = issue.subject 78 subject = issue.subject
79 if options[:truncate] 79 if options[:truncate]
80 subject = truncate(subject, :length => options[:truncate]) 80 subject = truncate(subject, :length => options[:truncate])
81 end 81 end
82 end 82 end
83 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, 83 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
84 :class => issue.css_classes, 84 :class => issue.css_classes,
85 :title => title 85 :title => title
86 s << ": #{h subject}" if subject 86 s << ": #{h subject}" if subject
87 s = "#{h issue.project} - " + s if options[:project] 87 s = "#{h issue.project} - " + s if options[:project]
88 s 88 s
102 # Generates a link to a SCM revision 102 # Generates a link to a SCM revision
103 # Options: 103 # Options:
104 # * :text - Link text (default to the formatted revision) 104 # * :text - Link text (default to the formatted revision)
105 def link_to_revision(revision, project, options={}) 105 def link_to_revision(revision, project, options={})
106 text = options.delete(:text) || format_revision(revision) 106 text = options.delete(:text) || format_revision(revision)
107 107 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
108 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision)) 108
109 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
110 :title => l(:label_revision_id, format_revision(revision)))
111 end
112
113 # Generates a link to a message
114 def link_to_message(message, options={}, html_options = nil)
115 link_to(
116 h(truncate(message.subject, :length => 60)),
117 { :controller => 'messages', :action => 'show',
118 :board_id => message.board_id,
119 :id => message.root,
120 :r => (message.parent_id && message.id),
121 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
122 }.merge(options),
123 html_options
124 )
109 end 125 end
110 126
111 # Generates a link to a project if active 127 # Generates a link to a project if active
112 # Examples: 128 # Examples:
113 # 129 #
114 # link_to_project(project) # => link to the specified project overview 130 # link_to_project(project) # => link to the specified project overview
115 # link_to_project(project, :action=>'settings') # => link to project settings 131 # link_to_project(project, :action=>'settings') # => link to project settings
116 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options 132 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
117 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) 133 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
118 # 134 #
142 158
143 def prompt_to_remote(name, text, param, url, html_options = {}) 159 def prompt_to_remote(name, text, param, url, html_options = {})
144 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;" 160 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
145 link_to name, {}, html_options 161 link_to name, {}, html_options
146 end 162 end
147 163
148 def format_activity_title(text) 164 def format_activity_title(text)
149 h(truncate_single_line(text, :length => 100)) 165 h(truncate_single_line(text, :length => 100))
150 end 166 end
151 167
152 def format_activity_day(date) 168 def format_activity_day(date)
153 date == Date.today ? l(:label_today).titleize : format_date(date) 169 date == Date.today ? l(:label_today).titleize : format_date(date)
154 end 170 end
155 171
156 def format_activity_description(text) 172 def format_activity_description(text)
157 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />") 173 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
158 end 174 end
159 175
160 def format_version_name(version) 176 def format_version_name(version)
162 h(version) 178 h(version)
163 else 179 else
164 h("#{version.project} - #{version}") 180 h("#{version.project} - #{version}")
165 end 181 end
166 end 182 end
167 183
168 def due_date_distance_in_words(date) 184 def due_date_distance_in_words(date)
169 if date 185 if date
170 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) 186 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
171 end 187 end
172 end 188 end
173 189
174 def render_page_hierarchy(pages, node=nil) 190 def render_page_hierarchy(pages, node=nil, options={})
175 content = '' 191 content = ''
176 if pages[node] 192 if pages[node]
177 content << "<ul class=\"pages-hierarchy\">\n" 193 content << "<ul class=\"pages-hierarchy\">\n"
178 pages[node].each do |page| 194 pages[node].each do |page|
179 content << "<li>" 195 content << "<li>"
180 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}, 196 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
181 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) 197 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
182 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id] 198 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
183 content << "</li>\n" 199 content << "</li>\n"
184 end 200 end
185 content << "</ul>\n" 201 content << "</ul>\n"
186 end 202 end
187 content 203 content
188 end 204 end
189 205
190 # Renders flash messages 206 # Renders flash messages
191 def render_flash_messages 207 def render_flash_messages
192 s = '' 208 s = ''
193 flash.each do |k,v| 209 flash.each do |k,v|
194 s << content_tag('div', v, :class => "flash #{k}") 210 s << content_tag('div', v, :class => "flash #{k}")
195 end 211 end
196 s 212 s
197 end 213 end
198 214
199 # Renders tabs and their content 215 # Renders tabs and their content
200 def render_tabs(tabs) 216 def render_tabs(tabs)
201 if tabs.any? 217 if tabs.any?
202 render :partial => 'common/tabs', :locals => {:tabs => tabs} 218 render :partial => 'common/tabs', :locals => {:tabs => tabs}
203 else 219 else
204 content_tag 'p', l(:label_no_data), :class => "nodata" 220 content_tag 'p', l(:label_no_data), :class => "nodata"
205 end 221 end
206 end 222 end
207 223
208 # Renders the project quick-jump box 224 # Renders the project quick-jump box
209 def render_project_jump_box 225 def render_project_jump_box
210 # Retrieve them now to avoid a COUNT query 226 return unless User.current.logged?
211 projects = User.current.projects.all 227 projects = User.current.memberships.collect(&:project).compact.uniq
212 if projects.any? 228 if projects.any?
213 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' + 229 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
214 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" + 230 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
215 '<option value="" disabled="disabled">---</option>' 231 '<option value="" disabled="disabled">---</option>'
216 s << project_tree_options_for_select(projects, :selected => @project) do |p| 232 s << project_tree_options_for_select(projects, :selected => @project) do |p|
218 end 234 end
219 s << '</select>' 235 s << '</select>'
220 s 236 s
221 end 237 end
222 end 238 end
223 239
224 def project_tree_options_for_select(projects, options = {}) 240 def project_tree_options_for_select(projects, options = {})
225 s = '' 241 s = ''
226 project_tree(projects) do |project, level| 242 project_tree(projects) do |project, level|
227 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '') 243 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
228 tag_options = {:value => project.id} 244 tag_options = {:value => project.id}
234 tag_options.merge!(yield(project)) if block_given? 250 tag_options.merge!(yield(project)) if block_given?
235 s << content_tag('option', name_prefix + h(project), tag_options) 251 s << content_tag('option', name_prefix + h(project), tag_options)
236 end 252 end
237 s 253 s
238 end 254 end
239 255
240 # Yields the given block for each project with its level in the tree 256 # Yields the given block for each project with its level in the tree
241 # 257 #
242 # Wrapper for Project#project_tree 258 # Wrapper for Project#project_tree
243 def project_tree(projects, &block) 259 def project_tree(projects, &block)
244 Project.project_tree(projects, &block) 260 Project.project_tree(projects, &block)
245 end 261 end
246 262
247 def project_nested_ul(projects, &block) 263 def project_nested_ul(projects, &block)
248 s = '' 264 s = ''
249 if projects.any? 265 if projects.any?
250 ancestors = [] 266 ancestors = []
251 projects.sort_by(&:lft).each do |project| 267 projects.sort_by(&:lft).each do |project|
252 if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) 268 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
253 s << "<ul>\n" 269 s << "<ul>\n"
254 else 270 else
255 ancestors.pop 271 ancestors.pop
256 s << "</li>" 272 s << "</li>"
257 while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) 273 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
258 ancestors.pop 274 ancestors.pop
259 s << "</ul></li>\n" 275 s << "</ul></li>\n"
260 end 276 end
261 end 277 end
262 s << "<li>" 278 s << "<li>"
265 end 281 end
266 s << ("</li></ul>\n" * ancestors.size) 282 s << ("</li></ul>\n" * ancestors.size)
267 end 283 end
268 s 284 s
269 end 285 end
270 286
271 def principals_check_box_tags(name, principals) 287 def principals_check_box_tags(name, principals)
272 s = '' 288 s = ''
273 principals.sort.each do |principal| 289 principals.sort.each do |principal|
274 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n" 290 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
275 end 291 end
276 s 292 s
277 end 293 end
278 294
279 # Truncates and returns the string as a single line 295 # Truncates and returns the string as a single line
280 def truncate_single_line(string, *args) 296 def truncate_single_line(string, *args)
281 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') 297 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
282 end 298 end
283 299
284 # Truncates at line break after 250 characters or options[:length] 300 # Truncates at line break after 250 characters or options[:length]
285 def truncate_lines(string, options={}) 301 def truncate_lines(string, options={})
286 length = options[:length] || 250 302 length = options[:length] || 250
287 if string.to_s =~ /\A(.{#{length}}.*?)$/m 303 if string.to_s =~ /\A(.{#{length}}.*?)$/m
288 "#{$1}..." 304 "#{$1}..."
296 end 312 end
297 313
298 def authoring(created, author, options={}) 314 def authoring(created, author, options={})
299 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)) 315 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
300 end 316 end
301 317
302 def time_tag(time) 318 def time_tag(time)
303 text = distance_of_time_in_words(Time.now, time) 319 text = distance_of_time_in_words(Time.now, time)
304 if @project 320 if @project
305 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time)) 321 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
306 else 322 else
318 334
319 def pagination_links_full(paginator, count=nil, options={}) 335 def pagination_links_full(paginator, count=nil, options={})
320 page_param = options.delete(:page_param) || :page 336 page_param = options.delete(:page_param) || :page
321 per_page_links = options.delete(:per_page_links) 337 per_page_links = options.delete(:per_page_links)
322 url_param = params.dup 338 url_param = params.dup
323 # don't reuse query params if filters are present
324 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
325 339
326 html = '' 340 html = ''
327 if paginator.current.previous 341 if paginator.current.previous
328 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' 342 html << link_to_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
329 end 343 end
330 344
331 html << (pagination_links_each(paginator, options) do |n| 345 html << (pagination_links_each(paginator, options) do |n|
332 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n)) 346 link_to_content_update(n.to_s, url_param.merge(page_param => n))
333 end || '') 347 end || '')
334 348
335 if paginator.current.next 349 if paginator.current.next
336 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next)) 350 html << ' ' + link_to_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
337 end 351 end
338 352
339 unless count.nil? 353 unless count.nil?
340 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" 354 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
341 if per_page_links != false && links = per_page_links(paginator.items_per_page) 355 if per_page_links != false && links = per_page_links(paginator.items_per_page)
343 end 357 end
344 end 358 end
345 359
346 html 360 html
347 end 361 end
348 362
349 def per_page_links(selected=nil) 363 def per_page_links(selected=nil)
350 url_param = params.dup
351 url_param.clear if url_param.has_key?(:set_filter)
352
353 links = Setting.per_page_options_array.collect do |n| 364 links = Setting.per_page_options_array.collect do |n|
354 n == selected ? n : link_to_remote(n, {:update => "content", 365 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
355 :url => params.dup.merge(:per_page => n),
356 :method => :get},
357 {:href => url_for(url_param.merge(:per_page => n))})
358 end 366 end
359 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil 367 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
360 end 368 end
361 369
362 def reorder_links(name, url) 370 def reorder_links(name, url)
363 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) + 371 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
364 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) + 372 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
365 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) + 373 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
366 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest)) 374 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
368 376
369 def breadcrumb(*args) 377 def breadcrumb(*args)
370 elements = args.flatten 378 elements = args.flatten
371 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil 379 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
372 end 380 end
373 381
374 def other_formats_links(&block) 382 def other_formats_links(&block)
375 concat('<p class="other-formats">' + l(:label_export_to)) 383 concat('<p class="other-formats">' + l(:label_export_to))
376 yield Redmine::Views::OtherFormatsBuilder.new(self) 384 yield Redmine::Views::OtherFormatsBuilder.new(self)
377 concat('</p>') 385 concat('</p>')
378 end 386 end
379 387
380 def page_header_title 388 def page_header_title
381 if @project.nil? || @project.new_record? 389 if @project.nil? || @project.new_record?
382 h(Setting.app_title) 390 h(Setting.app_title)
383 else 391 else
384 b = [] 392 b = []
385 ancestors = (@project.root? ? [] : @project.ancestors.visible) 393 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
386 if ancestors.any? 394 if ancestors.any?
387 root = ancestors.shift 395 root = ancestors.shift
388 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') 396 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
389 if ancestors.size > 2 397 if ancestors.size > 2
390 b << '&#8230;' 398 b << '&#8230;'
447 return '' if text.blank? 455 return '' if text.blank?
448 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) 456 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
449 only_path = options.delete(:only_path) == false ? false : true 457 only_path = options.delete(:only_path) == false ? false : true
450 458
451 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) } 459 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
452 460
453 parse_non_pre_blocks(text) do |text| 461 @parsed_headings = []
462 text = parse_non_pre_blocks(text) do |text|
454 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name| 463 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
455 send method_name, text, project, obj, attr, only_path, options 464 send method_name, text, project, obj, attr, only_path, options
456 end 465 end
457 end 466 end
458 end 467
459 468 if @parsed_headings.any?
469 replace_toc(text, @parsed_headings)
470 end
471
472 text
473 end
474
460 def parse_non_pre_blocks(text) 475 def parse_non_pre_blocks(text)
461 s = StringScanner.new(text) 476 s = StringScanner.new(text)
462 tags = [] 477 tags = []
463 parsed = '' 478 parsed = ''
464 while !s.eos? 479 while !s.eos?
483 while tag = tags.pop 498 while tag = tags.pop
484 parsed << "</#{tag}>" 499 parsed << "</#{tag}>"
485 end 500 end
486 parsed 501 parsed
487 end 502 end
488 503
489 def parse_inline_attachments(text, project, obj, attr, only_path, options) 504 def parse_inline_attachments(text, project, obj, attr, only_path, options)
490 # when using an image link, try to use an attachment, if possible 505 # when using an image link, try to use an attachment, if possible
491 if options[:attachments] || (obj && obj.respond_to?(:attachments)) 506 if options[:attachments] || (obj && obj.respond_to?(:attachments))
492 attachments = nil 507 attachments = nil
493 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| 508 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
494 filename, ext, alt, alttext = $1.downcase, $2, $3, $4 509 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
495 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse 510 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
496 # search for the picture in attachments 511 # search for the picture in attachments
497 if found = attachments.detect { |att| att.filename.downcase == filename } 512 if found = attachments.detect { |att| att.filename.downcase == filename }
498 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found 513 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
499 desc = found.description.to_s.gsub('"', '') 514 desc = found.description.to_s.gsub('"', '')
552 else 567 else
553 all 568 all
554 end 569 end
555 end 570 end
556 end 571 end
557 572
558 # Redmine links 573 # Redmine links
559 # 574 #
560 # Examples: 575 # Examples:
561 # Issues: 576 # Issues:
562 # #52 -> Link to issue #52 577 # #52 -> Link to issue #52
577 # source:some/file -> Link to the file located at /some/file in the project's repository 592 # source:some/file -> Link to the file located at /some/file in the project's repository
578 # source:some/file@52 -> Link to the file's revision 52 593 # source:some/file@52 -> Link to the file's revision 52
579 # source:some/file#L120 -> Link to line 120 of the file 594 # source:some/file#L120 -> Link to line 120 of the file
580 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 595 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
581 # export:some/file -> Force the download of the file 596 # export:some/file -> Force the download of the file
582 # Forum messages: 597 # Forum messages:
583 # message#1218 -> Link to message with id 1218 598 # message#1218 -> Link to message with id 1218
599 #
600 # Links can refer other objects from other projects, using project identifier:
601 # identifier:r52
602 # identifier:document:"Some document"
603 # identifier:version:1.0.0
604 # identifier:source:some/file
584 def parse_redmine_links(text, project, obj, attr, only_path, options) 605 def parse_redmine_links(text, project, obj, attr, only_path, options)
585 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| 606 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
586 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8 607 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
587 link = nil 608 link = nil
609 if project_identifier
610 project = Project.visible.find_by_identifier(project_identifier)
611 end
588 if esc.nil? 612 if esc.nil?
589 if prefix.nil? && sep == 'r' 613 if prefix.nil? && sep == 'r'
590 if project && (changeset = project.changesets.find_by_revision(identifier)) 614 # project.changesets.visible raises an SQL error because of a double join on repositories
591 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, 615 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
616 link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
592 :class => 'changeset', 617 :class => 'changeset',
593 :title => truncate_single_line(changeset.comments, :length => 100)) 618 :title => truncate_single_line(changeset.comments, :length => 100))
594 end 619 end
595 elsif sep == '#' 620 elsif sep == '#'
596 oid = identifier.to_i 621 oid = identifier.to_i
600 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid}, 625 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
601 :class => issue.css_classes, 626 :class => issue.css_classes,
602 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") 627 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
603 end 628 end
604 when 'document' 629 when 'document'
605 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current)) 630 if document = Document.visible.find_by_id(oid)
606 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, 631 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
607 :class => 'document' 632 :class => 'document'
608 end 633 end
609 when 'version' 634 when 'version'
610 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current)) 635 if version = Version.visible.find_by_id(oid)
611 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, 636 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
612 :class => 'version' 637 :class => 'version'
613 end 638 end
614 when 'message' 639 when 'message'
615 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current)) 640 if message = Message.visible.find_by_id(oid, :include => :parent)
616 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path, 641 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
617 :controller => 'messages',
618 :action => 'show',
619 :board_id => message.board,
620 :id => message.root,
621 :anchor => (message.parent ? "message-#{message.id}" : nil)},
622 :class => 'message'
623 end 642 end
624 when 'project' 643 when 'project'
625 if p = Project.visible.find_by_id(oid) 644 if p = Project.visible.find_by_id(oid)
626 link = link_to_project(p, {:only_path => only_path}, :class => 'project') 645 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
627 end 646 end
629 elsif sep == ':' 648 elsif sep == ':'
630 # removes the double quotes if any 649 # removes the double quotes if any
631 name = identifier.gsub(%r{^"(.*)"$}, "\\1") 650 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
632 case prefix 651 case prefix
633 when 'document' 652 when 'document'
634 if project && document = project.documents.find_by_title(name) 653 if project && document = project.documents.visible.find_by_title(name)
635 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, 654 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
636 :class => 'document' 655 :class => 'document'
637 end 656 end
638 when 'version' 657 when 'version'
639 if project && version = project.versions.find_by_name(name) 658 if project && version = project.versions.visible.find_by_name(name)
640 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, 659 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
641 :class => 'version' 660 :class => 'version'
642 end 661 end
643 when 'commit' 662 when 'commit'
644 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"])) 663 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
645 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, 664 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
646 :class => 'changeset', 665 :class => 'changeset',
647 :title => truncate_single_line(changeset.comments, :length => 100) 666 :title => truncate_single_line(changeset.comments, :length => 100)
648 end 667 end
649 when 'source', 'export' 668 when 'source', 'export'
650 if project && project.repository 669 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
651 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} 670 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
652 path, rev, anchor = $1, $3, $5 671 path, rev, anchor = $1, $3, $5
653 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, 672 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
654 :path => to_path_param(path), 673 :path => to_path_param(path),
655 :rev => rev, 674 :rev => rev,
656 :anchor => anchor, 675 :anchor => anchor,
657 :format => (prefix == 'export' ? 'raw' : nil)}, 676 :format => (prefix == 'export' ? 'raw' : nil)},
658 :class => (prefix == 'export' ? 'source download' : 'source') 677 :class => (prefix == 'export' ? 'source download' : 'source')
668 link = link_to_project(p, {:only_path => only_path}, :class => 'project') 687 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
669 end 688 end
670 end 689 end
671 end 690 end
672 end 691 end
673 leading + (link || "#{prefix}#{sep}#{identifier}") 692 leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
674 end 693 end
675 end 694 end
676 695
677 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
678 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE) 696 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
679 697
680 # Headings and TOC 698 # Headings and TOC
681 # Adds ids and links to headings and renders the TOC if needed unless options[:headings] is set to false 699 # Adds ids and links to headings unless options[:headings] is set to false
682 def parse_headings(text, project, obj, attr, only_path, options) 700 def parse_headings(text, project, obj, attr, only_path, options)
683 headings = [] 701 return if options[:headings] == false
702
684 text.gsub!(HEADING_RE) do 703 text.gsub!(HEADING_RE) do
685 level, attrs, content = $1.to_i, $2, $3 704 level, attrs, content = $1.to_i, $2, $3
686 item = strip_tags(content).strip 705 item = strip_tags(content).strip
687 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') 706 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
688 headings << [level, anchor, item] 707 @parsed_headings << [level, anchor, item]
689 "<h#{level} #{attrs} id=\"#{anchor}\">#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>" 708 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
690 end unless options[:headings] == false 709 end
691 710 end
711
712 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
713
714 # Renders the TOC with given headings
715 def replace_toc(text, headings)
692 text.gsub!(TOC_RE) do 716 text.gsub!(TOC_RE) do
693 if headings.empty? 717 if headings.empty?
694 '' 718 ''
695 else 719 else
696 div_class = 'toc' 720 div_class = 'toc'
767 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') + 791 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
768 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '') 792 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
769 ), :class => 'progress', :style => "width: #{width};") + 793 ), :class => 'progress', :style => "width: #{width};") +
770 content_tag('p', legend, :class => 'pourcent') 794 content_tag('p', legend, :class => 'pourcent')
771 end 795 end
772 796
773 def checked_image(checked=true) 797 def checked_image(checked=true)
774 if checked 798 if checked
775 image_tag 'toggle_check.png' 799 image_tag 'toggle_check.png'
776 end 800 end
777 end 801 end
778 802
779 def context_menu(url) 803 def context_menu(url)
780 unless @context_menu_included 804 unless @context_menu_included
781 content_for :header_tags do 805 content_for :header_tags do
782 javascript_include_tag('context_menu') + 806 javascript_include_tag('context_menu') +
783 stylesheet_link_tag('context_menu') 807 stylesheet_link_tag('context_menu')
821 start_of_week = case Setting.start_of_week.to_i 845 start_of_week = case Setting.start_of_week.to_i
822 when 1 846 when 1
823 'Calendar._FD = 1;' # Monday 847 'Calendar._FD = 1;' # Monday
824 when 7 848 when 7
825 'Calendar._FD = 0;' # Sunday 849 'Calendar._FD = 0;' # Sunday
850 when 6
851 'Calendar._FD = 6;' # Saturday
826 else 852 else
827 '' # use language 853 '' # use language
828 end 854 end
829 855
830 javascript_include_tag('calendar/calendar') + 856 javascript_include_tag('calendar/calendar') +
831 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") + 857 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
832 javascript_tag(start_of_week) + 858 javascript_tag(start_of_week) +
833 javascript_include_tag('calendar/calendar-setup') + 859 javascript_include_tag('calendar/calendar-setup') +
834 stylesheet_link_tag('calendar') 860 stylesheet_link_tag('calendar')
835 end 861 end
836 end 862 end
837 end 863 end
861 else 887 else
862 '' 888 ''
863 end 889 end
864 end 890 end
865 891
892 # Returns the javascript tags that are included in the html layout head
893 def javascript_heads
894 tags = javascript_include_tag(:defaults)
895 unless User.current.pref.warn_on_leaving_unsaved == '0'
896 tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
897 end
898 tags
899 end
900
866 def favicon 901 def favicon
867 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />" 902 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
903 end
904
905 def robot_exclusion_tag
906 '<meta name="robots" content="noindex,follow,noarchive" />'
907 end
908
909 # Returns true if arg is expected in the API response
910 def include_in_api_response?(arg)
911 unless @included_in_api_response
912 param = params[:include]
913 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
914 @included_in_api_response.collect!(&:strip)
915 end
916 @included_in_api_response.include?(arg.to_s)
917 end
918
919 # Returns options or nil if nometa param or X-Redmine-Nometa header
920 # was set in the request
921 def api_meta(options)
922 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
923 # compatibility mode for activeresource clients that raise
924 # an error when unserializing an array with attributes
925 nil
926 else
927 options
928 end
868 end 929 end
869 930
870 private 931 private
871 932
872 def wiki_helper 933 def wiki_helper
873 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) 934 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
874 extend helper 935 extend helper
875 return self 936 return self
876 end 937 end
877 938
878 def link_to_remote_content_update(text, url_params) 939 def link_to_content_update(text, url_params = {}, html_options = {})
879 link_to_remote(text, 940 link_to(text, url_params, html_options)
880 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, 941 end
881 {:href => url_for(:params => url_params)}
882 )
883 end
884
885 end 942 end