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