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