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