##// END OF EJS Templates
Rails3: helper: use html_safe at simple_format_without_paragraph of ApplicationHelper...
Toshi MARUYAMA -
r7465:55f10b90f4ba
parent child
Show More
@@ -1,977 +1,978
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'forwardable'
19 19 require 'cgi'
20 20
21 21 module ApplicationHelper
22 22 include Redmine::WikiFormatting::Macros::Definitions
23 23 include Redmine::I18n
24 24 include GravatarHelper::PublicMethods
25 25
26 26 extend Forwardable
27 27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28 28
29 29 # Return true if user is authorized for controller/action, otherwise false
30 30 def authorize_for(controller, action)
31 31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
32 32 end
33 33
34 34 # Display a link if user is authorized
35 35 #
36 36 # @param [String] name Anchor text (passed to link_to)
37 37 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
38 38 # @param [optional, Hash] html_options Options passed to link_to
39 39 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
40 40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
42 42 end
43 43
44 44 # Display a link to remote if user is authorized
45 45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
46 46 url = options[:url] || {}
47 47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
48 48 end
49 49
50 50 # Displays a link to user's account page if active
51 51 def link_to_user(user, options={})
52 52 if user.is_a?(User)
53 53 name = h(user.name(options[:format]))
54 54 if user.active?
55 55 link_to name, :controller => 'users', :action => 'show', :id => user
56 56 else
57 57 name
58 58 end
59 59 else
60 60 h(user.to_s)
61 61 end
62 62 end
63 63
64 64 # Displays a link to +issue+ with its subject.
65 65 # Examples:
66 66 #
67 67 # link_to_issue(issue) # => Defect #6: This is the subject
68 68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
69 69 # link_to_issue(issue, :subject => false) # => Defect #6
70 70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
71 71 #
72 72 def link_to_issue(issue, options={})
73 73 title = nil
74 74 subject = nil
75 75 if options[:subject] == false
76 76 title = truncate(issue.subject, :length => 60)
77 77 else
78 78 subject = issue.subject
79 79 if options[:truncate]
80 80 subject = truncate(subject, :length => options[:truncate])
81 81 end
82 82 end
83 83 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
84 84 :class => issue.css_classes,
85 85 :title => title
86 86 s << ": #{h subject}" if subject
87 87 s = "#{h issue.project} - " + s if options[:project]
88 88 s
89 89 end
90 90
91 91 # Generates a link to an attachment.
92 92 # Options:
93 93 # * :text - Link text (default to attachment filename)
94 94 # * :download - Force download (default: false)
95 95 def link_to_attachment(attachment, options={})
96 96 text = options.delete(:text) || attachment.filename
97 97 action = options.delete(:download) ? 'download' : 'show'
98 98
99 99 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
100 100 end
101 101
102 102 # Generates a link to a SCM revision
103 103 # Options:
104 104 # * :text - Link text (default to the formatted revision)
105 105 def link_to_revision(revision, project, options={})
106 106 text = options.delete(:text) || format_revision(revision)
107 107 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
108 108
109 109 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
110 110 :title => l(:label_revision_id, format_revision(revision)))
111 111 end
112 112
113 113 # Generates a link to a message
114 114 def link_to_message(message, options={}, html_options = nil)
115 115 link_to(
116 116 h(truncate(message.subject, :length => 60)),
117 117 { :controller => 'messages', :action => 'show',
118 118 :board_id => message.board_id,
119 119 :id => message.root,
120 120 :r => (message.parent_id && message.id),
121 121 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
122 122 }.merge(options),
123 123 html_options
124 124 )
125 125 end
126 126
127 127 # Generates a link to a project if active
128 128 # Examples:
129 129 #
130 130 # link_to_project(project) # => link to the specified project overview
131 131 # link_to_project(project, :action=>'settings') # => link to project settings
132 132 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
133 133 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
134 134 #
135 135 def link_to_project(project, options={}, html_options = nil)
136 136 if project.active?
137 137 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
138 138 link_to(h(project), url, html_options)
139 139 else
140 140 h(project)
141 141 end
142 142 end
143 143
144 144 def toggle_link(name, id, options={})
145 145 onclick = "Element.toggle('#{id}'); "
146 146 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
147 147 onclick << "return false;"
148 148 link_to(name, "#", :onclick => onclick)
149 149 end
150 150
151 151 def image_to_function(name, function, html_options = {})
152 152 html_options.symbolize_keys!
153 153 tag(:input, html_options.merge({
154 154 :type => "image", :src => image_path(name),
155 155 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
156 156 }))
157 157 end
158 158
159 159 def prompt_to_remote(name, text, param, url, html_options = {})
160 160 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
161 161 link_to name, {}, html_options
162 162 end
163 163
164 164 def format_activity_title(text)
165 165 h(truncate_single_line(text, :length => 100))
166 166 end
167 167
168 168 def format_activity_day(date)
169 169 date == Date.today ? l(:label_today).titleize : format_date(date)
170 170 end
171 171
172 172 def format_activity_description(text)
173 173 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
174 174 end
175 175
176 176 def format_version_name(version)
177 177 if version.project == @project
178 178 h(version)
179 179 else
180 180 h("#{version.project} - #{version}")
181 181 end
182 182 end
183 183
184 184 def due_date_distance_in_words(date)
185 185 if date
186 186 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
187 187 end
188 188 end
189 189
190 190 def render_page_hierarchy(pages, node=nil, options={})
191 191 content = ''
192 192 if pages[node]
193 193 content << "<ul class=\"pages-hierarchy\">\n"
194 194 pages[node].each do |page|
195 195 content << "<li>"
196 196 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
197 197 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
198 198 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
199 199 content << "</li>\n"
200 200 end
201 201 content << "</ul>\n"
202 202 end
203 203 content.html_safe
204 204 end
205 205
206 206 # Renders flash messages
207 207 def render_flash_messages
208 208 s = ''
209 209 flash.each do |k,v|
210 210 s << content_tag('div', v, :class => "flash #{k}")
211 211 end
212 212 s.html_safe
213 213 end
214 214
215 215 # Renders tabs and their content
216 216 def render_tabs(tabs)
217 217 if tabs.any?
218 218 render :partial => 'common/tabs', :locals => {:tabs => tabs}
219 219 else
220 220 content_tag 'p', l(:label_no_data), :class => "nodata"
221 221 end
222 222 end
223 223
224 224 # Renders the project quick-jump box
225 225 def render_project_jump_box
226 226 return unless User.current.logged?
227 227 projects = User.current.memberships.collect(&:project).compact.uniq
228 228 if projects.any?
229 229 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
230 230 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
231 231 '<option value="" disabled="disabled">---</option>'
232 232 s << project_tree_options_for_select(projects, :selected => @project) do |p|
233 233 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
234 234 end
235 235 s << '</select>'
236 236 s.html_safe
237 237 end
238 238 end
239 239
240 240 def project_tree_options_for_select(projects, options = {})
241 241 s = ''
242 242 project_tree(projects) do |project, level|
243 243 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
244 244 tag_options = {:value => project.id}
245 245 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
246 246 tag_options[:selected] = 'selected'
247 247 else
248 248 tag_options[:selected] = nil
249 249 end
250 250 tag_options.merge!(yield(project)) if block_given?
251 251 s << content_tag('option', name_prefix + h(project), tag_options)
252 252 end
253 253 s.html_safe
254 254 end
255 255
256 256 # Yields the given block for each project with its level in the tree
257 257 #
258 258 # Wrapper for Project#project_tree
259 259 def project_tree(projects, &block)
260 260 Project.project_tree(projects, &block)
261 261 end
262 262
263 263 def project_nested_ul(projects, &block)
264 264 s = ''
265 265 if projects.any?
266 266 ancestors = []
267 267 projects.sort_by(&:lft).each do |project|
268 268 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
269 269 s << "<ul>\n"
270 270 else
271 271 ancestors.pop
272 272 s << "</li>"
273 273 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
274 274 ancestors.pop
275 275 s << "</ul></li>\n"
276 276 end
277 277 end
278 278 s << "<li>"
279 279 s << yield(project).to_s
280 280 ancestors << project
281 281 end
282 282 s << ("</li></ul>\n" * ancestors.size)
283 283 end
284 284 s.html_safe
285 285 end
286 286
287 287 def principals_check_box_tags(name, principals)
288 288 s = ''
289 289 principals.sort.each do |principal|
290 290 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
291 291 end
292 292 s.html_safe
293 293 end
294 294
295 295 # Returns a string for users/groups option tags
296 296 def principals_options_for_select(collection, selected=nil)
297 297 s = ''
298 298 groups = ''
299 299 collection.sort.each do |element|
300 300 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
301 301 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
302 302 end
303 303 unless groups.empty?
304 304 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
305 305 end
306 306 s
307 307 end
308 308
309 309 # Truncates and returns the string as a single line
310 310 def truncate_single_line(string, *args)
311 311 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
312 312 end
313 313
314 314 # Truncates at line break after 250 characters or options[:length]
315 315 def truncate_lines(string, options={})
316 316 length = options[:length] || 250
317 317 if string.to_s =~ /\A(.{#{length}}.*?)$/m
318 318 "#{$1}..."
319 319 else
320 320 string
321 321 end
322 322 end
323 323
324 324 def html_hours(text)
325 325 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
326 326 end
327 327
328 328 def authoring(created, author, options={})
329 329 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
330 330 end
331 331
332 332 def time_tag(time)
333 333 text = distance_of_time_in_words(Time.now, time)
334 334 if @project
335 335 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
336 336 else
337 337 content_tag('acronym', text, :title => format_time(time))
338 338 end
339 339 end
340 340
341 341 def syntax_highlight(name, content)
342 342 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
343 343 end
344 344
345 345 def to_path_param(path)
346 346 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
347 347 end
348 348
349 349 def pagination_links_full(paginator, count=nil, options={})
350 350 page_param = options.delete(:page_param) || :page
351 351 per_page_links = options.delete(:per_page_links)
352 352 url_param = params.dup
353 353
354 354 html = ''
355 355 if paginator.current.previous
356 356 # \xc2\xab(utf-8) = &#171;
357 357 html << link_to_content_update(
358 358 "\xc2\xab " + l(:label_previous),
359 359 url_param.merge(page_param => paginator.current.previous)) + ' '
360 360 end
361 361
362 362 html << (pagination_links_each(paginator, options) do |n|
363 363 link_to_content_update(n.to_s, url_param.merge(page_param => n))
364 364 end || '')
365 365
366 366 if paginator.current.next
367 367 # \xc2\xbb(utf-8) = &#187;
368 368 html << ' ' + link_to_content_update(
369 369 (l(:label_next) + " \xc2\xbb"),
370 370 url_param.merge(page_param => paginator.current.next))
371 371 end
372 372
373 373 unless count.nil?
374 374 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
375 375 if per_page_links != false && links = per_page_links(paginator.items_per_page)
376 376 html << " | #{links}"
377 377 end
378 378 end
379 379
380 380 html.html_safe
381 381 end
382 382
383 383 def per_page_links(selected=nil)
384 384 links = Setting.per_page_options_array.collect do |n|
385 385 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
386 386 end
387 387 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
388 388 end
389 389
390 390 def reorder_links(name, url)
391 391 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
392 392 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
393 393 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
394 394 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
395 395 end
396 396
397 397 def breadcrumb(*args)
398 398 elements = args.flatten
399 399 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
400 400 end
401 401
402 402 def other_formats_links(&block)
403 403 concat('<p class="other-formats">' + l(:label_export_to))
404 404 yield Redmine::Views::OtherFormatsBuilder.new(self)
405 405 concat('</p>')
406 406 end
407 407
408 408 def page_header_title
409 409 if @project.nil? || @project.new_record?
410 410 h(Setting.app_title)
411 411 else
412 412 b = []
413 413 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
414 414 if ancestors.any?
415 415 root = ancestors.shift
416 416 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
417 417 if ancestors.size > 2
418 418 b << "\xe2\x80\xa6"
419 419 ancestors = ancestors[-2, 2]
420 420 end
421 421 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
422 422 end
423 423 b << h(@project)
424 424 b.join(" \xc2\xbb ")
425 425 end
426 426 end
427 427
428 428 def html_title(*args)
429 429 if args.empty?
430 430 title = []
431 431 title << @project.name if @project
432 432 title += @html_title if @html_title
433 433 title << Setting.app_title
434 434 title.select {|t| !t.blank? }.join(' - ')
435 435 else
436 436 @html_title ||= []
437 437 @html_title += args
438 438 end
439 439 end
440 440
441 441 # Returns the theme, controller name, and action as css classes for the
442 442 # HTML body.
443 443 def body_css_classes
444 444 css = []
445 445 if theme = Redmine::Themes.theme(Setting.ui_theme)
446 446 css << 'theme-' + theme.name
447 447 end
448 448
449 449 css << 'controller-' + params[:controller]
450 450 css << 'action-' + params[:action]
451 451 css.join(' ')
452 452 end
453 453
454 454 def accesskey(s)
455 455 Redmine::AccessKeys.key_for s
456 456 end
457 457
458 458 # Formats text according to system settings.
459 459 # 2 ways to call this method:
460 460 # * with a String: textilizable(text, options)
461 461 # * with an object and one of its attribute: textilizable(issue, :description, options)
462 462 def textilizable(*args)
463 463 options = args.last.is_a?(Hash) ? args.pop : {}
464 464 case args.size
465 465 when 1
466 466 obj = options[:object]
467 467 text = args.shift
468 468 when 2
469 469 obj = args.shift
470 470 attr = args.shift
471 471 text = obj.send(attr).to_s
472 472 else
473 473 raise ArgumentError, 'invalid arguments to textilizable'
474 474 end
475 475 return '' if text.blank?
476 476 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
477 477 only_path = options.delete(:only_path) == false ? false : true
478 478
479 479 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
480 480
481 481 @parsed_headings = []
482 482 text = parse_non_pre_blocks(text) do |text|
483 483 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
484 484 send method_name, text, project, obj, attr, only_path, options
485 485 end
486 486 end
487 487
488 488 if @parsed_headings.any?
489 489 replace_toc(text, @parsed_headings)
490 490 end
491 491
492 492 text
493 493 end
494 494
495 495 def parse_non_pre_blocks(text)
496 496 s = StringScanner.new(text)
497 497 tags = []
498 498 parsed = ''
499 499 while !s.eos?
500 500 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
501 501 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
502 502 if tags.empty?
503 503 yield text
504 504 end
505 505 parsed << text
506 506 if tag
507 507 if closing
508 508 if tags.last == tag.downcase
509 509 tags.pop
510 510 end
511 511 else
512 512 tags << tag.downcase
513 513 end
514 514 parsed << full_tag
515 515 end
516 516 end
517 517 # Close any non closing tags
518 518 while tag = tags.pop
519 519 parsed << "</#{tag}>"
520 520 end
521 521 parsed.html_safe
522 522 end
523 523
524 524 def parse_inline_attachments(text, project, obj, attr, only_path, options)
525 525 # when using an image link, try to use an attachment, if possible
526 526 if options[:attachments] || (obj && obj.respond_to?(:attachments))
527 527 attachments = nil
528 528 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
529 529 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
530 530 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
531 531 # search for the picture in attachments
532 532 if found = attachments.detect { |att| att.filename.downcase == filename }
533 533 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
534 534 desc = found.description.to_s.gsub('"', '')
535 535 if !desc.blank? && alttext.blank?
536 536 alt = " title=\"#{desc}\" alt=\"#{desc}\""
537 537 end
538 538 "src=\"#{image_url}\"#{alt}".html_safe
539 539 else
540 540 m.html_safe
541 541 end
542 542 end
543 543 end
544 544 end
545 545
546 546 # Wiki links
547 547 #
548 548 # Examples:
549 549 # [[mypage]]
550 550 # [[mypage|mytext]]
551 551 # wiki links can refer other project wikis, using project name or identifier:
552 552 # [[project:]] -> wiki starting page
553 553 # [[project:|mytext]]
554 554 # [[project:mypage]]
555 555 # [[project:mypage|mytext]]
556 556 def parse_wiki_links(text, project, obj, attr, only_path, options)
557 557 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
558 558 link_project = project
559 559 esc, all, page, title = $1, $2, $3, $5
560 560 if esc.nil?
561 561 if page =~ /^([^\:]+)\:(.*)$/
562 562 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
563 563 page = $2
564 564 title ||= $1 if page.blank?
565 565 end
566 566
567 567 if link_project && link_project.wiki
568 568 # extract anchor
569 569 anchor = nil
570 570 if page =~ /^(.+?)\#(.+)$/
571 571 page, anchor = $1, $2
572 572 end
573 573 anchor = sanitize_anchor_name(anchor) if anchor.present?
574 574 # check if page exists
575 575 wiki_page = link_project.wiki.find_page(page)
576 576 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
577 577 "##{anchor}"
578 578 else
579 579 case options[:wiki_links]
580 580 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
581 581 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
582 582 else
583 583 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
584 584 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
585 585 end
586 586 end
587 587 link_to(h(title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
588 588 else
589 589 # project or wiki doesn't exist
590 590 all.html_safe
591 591 end
592 592 else
593 593 all.html_safe
594 594 end
595 595 end
596 596 end
597 597
598 598 # Redmine links
599 599 #
600 600 # Examples:
601 601 # Issues:
602 602 # #52 -> Link to issue #52
603 603 # Changesets:
604 604 # r52 -> Link to revision 52
605 605 # commit:a85130f -> Link to scmid starting with a85130f
606 606 # Documents:
607 607 # document#17 -> Link to document with id 17
608 608 # document:Greetings -> Link to the document with title "Greetings"
609 609 # document:"Some document" -> Link to the document with title "Some document"
610 610 # Versions:
611 611 # version#3 -> Link to version with id 3
612 612 # version:1.0.0 -> Link to version named "1.0.0"
613 613 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
614 614 # Attachments:
615 615 # attachment:file.zip -> Link to the attachment of the current object named file.zip
616 616 # Source files:
617 617 # source:some/file -> Link to the file located at /some/file in the project's repository
618 618 # source:some/file@52 -> Link to the file's revision 52
619 619 # source:some/file#L120 -> Link to line 120 of the file
620 620 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
621 621 # export:some/file -> Force the download of the file
622 622 # Forum messages:
623 623 # message#1218 -> Link to message with id 1218
624 624 #
625 625 # Links can refer other objects from other projects, using project identifier:
626 626 # identifier:r52
627 627 # identifier:document:"Some document"
628 628 # identifier:version:1.0.0
629 629 # identifier:source:some/file
630 630 def parse_redmine_links(text, project, obj, attr, only_path, options)
631 631 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
632 632 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
633 633 link = nil
634 634 if project_identifier
635 635 project = Project.visible.find_by_identifier(project_identifier)
636 636 end
637 637 if esc.nil?
638 638 if prefix.nil? && sep == 'r'
639 639 # project.changesets.visible raises an SQL error because of a double join on repositories
640 640 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
641 641 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
642 642 :class => 'changeset',
643 643 :title => truncate_single_line(changeset.comments, :length => 100))
644 644 end
645 645 elsif sep == '#'
646 646 oid = identifier.to_i
647 647 case prefix
648 648 when nil
649 649 if issue = Issue.visible.find_by_id(oid, :include => :status)
650 650 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
651 651 :class => issue.css_classes,
652 652 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
653 653 end
654 654 when 'document'
655 655 if document = Document.visible.find_by_id(oid)
656 656 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
657 657 :class => 'document'
658 658 end
659 659 when 'version'
660 660 if version = Version.visible.find_by_id(oid)
661 661 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
662 662 :class => 'version'
663 663 end
664 664 when 'message'
665 665 if message = Message.visible.find_by_id(oid, :include => :parent)
666 666 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
667 667 end
668 668 when 'project'
669 669 if p = Project.visible.find_by_id(oid)
670 670 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
671 671 end
672 672 end
673 673 elsif sep == ':'
674 674 # removes the double quotes if any
675 675 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
676 676 case prefix
677 677 when 'document'
678 678 if project && document = project.documents.visible.find_by_title(name)
679 679 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
680 680 :class => 'document'
681 681 end
682 682 when 'version'
683 683 if project && version = project.versions.visible.find_by_name(name)
684 684 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
685 685 :class => 'version'
686 686 end
687 687 when 'commit'
688 688 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
689 689 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
690 690 :class => 'changeset',
691 691 :title => truncate_single_line(h(changeset.comments), :length => 100)
692 692 end
693 693 when 'source', 'export'
694 694 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
695 695 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
696 696 path, rev, anchor = $1, $3, $5
697 697 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
698 698 :path => to_path_param(path),
699 699 :rev => rev,
700 700 :anchor => anchor,
701 701 :format => (prefix == 'export' ? 'raw' : nil)},
702 702 :class => (prefix == 'export' ? 'source download' : 'source')
703 703 end
704 704 when 'attachment'
705 705 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
706 706 if attachments && attachment = attachments.detect {|a| a.filename == name }
707 707 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
708 708 :class => 'attachment'
709 709 end
710 710 when 'project'
711 711 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
712 712 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
713 713 end
714 714 end
715 715 end
716 716 end
717 717 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
718 718 end
719 719 end
720 720
721 721 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
722 722
723 723 # Headings and TOC
724 724 # Adds ids and links to headings unless options[:headings] is set to false
725 725 def parse_headings(text, project, obj, attr, only_path, options)
726 726 return if options[:headings] == false
727 727
728 728 text.gsub!(HEADING_RE) do
729 729 level, attrs, content = $1.to_i, $2, $3
730 730 item = strip_tags(content).strip
731 731 anchor = sanitize_anchor_name(item)
732 732 # used for single-file wiki export
733 733 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
734 734 @parsed_headings << [level, anchor, item]
735 735 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
736 736 end
737 737 end
738 738
739 739 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
740 740
741 741 # Renders the TOC with given headings
742 742 def replace_toc(text, headings)
743 743 text.gsub!(TOC_RE) do
744 744 if headings.empty?
745 745 ''
746 746 else
747 747 div_class = 'toc'
748 748 div_class << ' right' if $1 == '>'
749 749 div_class << ' left' if $1 == '<'
750 750 out = "<ul class=\"#{div_class}\"><li>"
751 751 root = headings.map(&:first).min
752 752 current = root
753 753 started = false
754 754 headings.each do |level, anchor, item|
755 755 if level > current
756 756 out << '<ul><li>' * (level - current)
757 757 elsif level < current
758 758 out << "</li></ul>\n" * (current - level) + "</li><li>"
759 759 elsif started
760 760 out << '</li><li>'
761 761 end
762 762 out << "<a href=\"##{anchor}\">#{item}</a>"
763 763 current = level
764 764 started = true
765 765 end
766 766 out << '</li></ul>' * (current - root)
767 767 out << '</li></ul>'
768 768 end
769 769 end
770 770 end
771 771
772 772 # Same as Rails' simple_format helper without using paragraphs
773 773 def simple_format_without_paragraph(text)
774 774 text.to_s.
775 775 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
776 776 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
777 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
777 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
778 html_safe
778 779 end
779 780
780 781 def lang_options_for_select(blank=true)
781 782 (blank ? [["(auto)", ""]] : []) +
782 783 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
783 784 end
784 785
785 786 def label_tag_for(name, option_tags = nil, options = {})
786 787 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
787 788 content_tag("label", label_text)
788 789 end
789 790
790 791 def labelled_tabular_form_for(name, object, options, &proc)
791 792 options[:html] ||= {}
792 793 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
793 794 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
794 795 end
795 796
796 797 def back_url_hidden_field_tag
797 798 back_url = params[:back_url] || request.env['HTTP_REFERER']
798 799 back_url = CGI.unescape(back_url.to_s)
799 800 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
800 801 end
801 802
802 803 def check_all_links(form_name)
803 804 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
804 805 " | ".html_safe +
805 806 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
806 807 end
807 808
808 809 def progress_bar(pcts, options={})
809 810 pcts = [pcts, pcts] unless pcts.is_a?(Array)
810 811 pcts = pcts.collect(&:round)
811 812 pcts[1] = pcts[1] - pcts[0]
812 813 pcts << (100 - pcts[1] - pcts[0])
813 814 width = options[:width] || '100px;'
814 815 legend = options[:legend] || ''
815 816 content_tag('table',
816 817 content_tag('tr',
817 818 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
818 819 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
819 820 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
820 821 ), :class => 'progress', :style => "width: #{width};").html_safe +
821 822 content_tag('p', legend, :class => 'pourcent').html_safe
822 823 end
823 824
824 825 def checked_image(checked=true)
825 826 if checked
826 827 image_tag 'toggle_check.png'
827 828 end
828 829 end
829 830
830 831 def context_menu(url)
831 832 unless @context_menu_included
832 833 content_for :header_tags do
833 834 javascript_include_tag('context_menu') +
834 835 stylesheet_link_tag('context_menu')
835 836 end
836 837 if l(:direction) == 'rtl'
837 838 content_for :header_tags do
838 839 stylesheet_link_tag('context_menu_rtl')
839 840 end
840 841 end
841 842 @context_menu_included = true
842 843 end
843 844 javascript_tag "new ContextMenu('#{ url_for(url) }')"
844 845 end
845 846
846 847 def context_menu_link(name, url, options={})
847 848 options[:class] ||= ''
848 849 if options.delete(:selected)
849 850 options[:class] << ' icon-checked disabled'
850 851 options[:disabled] = true
851 852 end
852 853 if options.delete(:disabled)
853 854 options.delete(:method)
854 855 options.delete(:confirm)
855 856 options.delete(:onclick)
856 857 options[:class] << ' disabled'
857 858 url = '#'
858 859 end
859 860 link_to h(name), url, options
860 861 end
861 862
862 863 def calendar_for(field_id)
863 864 include_calendar_headers_tags
864 865 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
865 866 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
866 867 end
867 868
868 869 def include_calendar_headers_tags
869 870 unless @calendar_headers_tags_included
870 871 @calendar_headers_tags_included = true
871 872 content_for :header_tags do
872 873 start_of_week = case Setting.start_of_week.to_i
873 874 when 1
874 875 'Calendar._FD = 1;' # Monday
875 876 when 7
876 877 'Calendar._FD = 0;' # Sunday
877 878 when 6
878 879 'Calendar._FD = 6;' # Saturday
879 880 else
880 881 '' # use language
881 882 end
882 883
883 884 javascript_include_tag('calendar/calendar') +
884 885 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
885 886 javascript_tag(start_of_week) +
886 887 javascript_include_tag('calendar/calendar-setup') +
887 888 stylesheet_link_tag('calendar')
888 889 end
889 890 end
890 891 end
891 892
892 893 def content_for(name, content = nil, &block)
893 894 @has_content ||= {}
894 895 @has_content[name] = true
895 896 super(name, content, &block)
896 897 end
897 898
898 899 def has_content?(name)
899 900 (@has_content && @has_content[name]) || false
900 901 end
901 902
902 903 def email_delivery_enabled?
903 904 !!ActionMailer::Base.perform_deliveries
904 905 end
905 906
906 907 # Returns the avatar image tag for the given +user+ if avatars are enabled
907 908 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
908 909 def avatar(user, options = { })
909 910 if Setting.gravatar_enabled?
910 911 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
911 912 email = nil
912 913 if user.respond_to?(:mail)
913 914 email = user.mail
914 915 elsif user.to_s =~ %r{<(.+?)>}
915 916 email = $1
916 917 end
917 918 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
918 919 else
919 920 ''
920 921 end
921 922 end
922 923
923 924 def sanitize_anchor_name(anchor)
924 925 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
925 926 end
926 927
927 928 # Returns the javascript tags that are included in the html layout head
928 929 def javascript_heads
929 930 tags = javascript_include_tag(:defaults)
930 931 unless User.current.pref.warn_on_leaving_unsaved == '0'
931 932 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
932 933 end
933 934 tags
934 935 end
935 936
936 937 def favicon
937 938 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
938 939 end
939 940
940 941 def robot_exclusion_tag
941 942 '<meta name="robots" content="noindex,follow,noarchive" />'
942 943 end
943 944
944 945 # Returns true if arg is expected in the API response
945 946 def include_in_api_response?(arg)
946 947 unless @included_in_api_response
947 948 param = params[:include]
948 949 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
949 950 @included_in_api_response.collect!(&:strip)
950 951 end
951 952 @included_in_api_response.include?(arg.to_s)
952 953 end
953 954
954 955 # Returns options or nil if nometa param or X-Redmine-Nometa header
955 956 # was set in the request
956 957 def api_meta(options)
957 958 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
958 959 # compatibility mode for activeresource clients that raise
959 960 # an error when unserializing an array with attributes
960 961 nil
961 962 else
962 963 options
963 964 end
964 965 end
965 966
966 967 private
967 968
968 969 def wiki_helper
969 970 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
970 971 extend helper
971 972 return self
972 973 end
973 974
974 975 def link_to_content_update(text, url_params = {}, html_options = {})
975 976 link_to(text, url_params, html_options)
976 977 end
977 978 end
General Comments 0
You need to be logged in to leave comments. Login now