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