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