##// END OF EJS Templates
remove trailing white-spaces from ApplicationHelper...
Toshi MARUYAMA -
r11845:e13814a59c70
parent child
Show More
@@ -1,1272 +1,1272
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.each do |principal|
320 320 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{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) || element.id.to_s == 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.to_a.include?(p)}
347 347 end
348 348 options
349 349 end
350 350
351 351 def option_tag(name, text, value, selected=nil, options={})
352 352 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
353 353 end
354 354
355 355 # Truncates and returns the string as a single line
356 356 def truncate_single_line(string, *args)
357 357 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
358 358 end
359 359
360 360 # Truncates at line break after 250 characters or options[:length]
361 361 def truncate_lines(string, options={})
362 362 length = options[:length] || 250
363 363 if string.to_s =~ /\A(.{#{length}}.*?)$/m
364 364 "#{$1}..."
365 365 else
366 366 string
367 367 end
368 368 end
369 369
370 370 def anchor(text)
371 371 text.to_s.gsub(' ', '_')
372 372 end
373 373
374 374 def html_hours(text)
375 375 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
376 376 end
377 377
378 378 def authoring(created, author, options={})
379 379 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
380 380 end
381 381
382 382 def time_tag(time)
383 383 text = distance_of_time_in_words(Time.now, time)
384 384 if @project
385 385 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
386 386 else
387 387 content_tag('acronym', text, :title => format_time(time))
388 388 end
389 389 end
390 390
391 391 def syntax_highlight_lines(name, content)
392 392 lines = []
393 393 syntax_highlight(name, content).each_line { |line| lines << line }
394 394 lines
395 395 end
396 396
397 397 def syntax_highlight(name, content)
398 398 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
399 399 end
400 400
401 401 def to_path_param(path)
402 402 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
403 403 str.blank? ? nil : str
404 404 end
405 405
406 406 def reorder_links(name, url, method = :post)
407 407 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
408 408 url.merge({"#{name}[move_to]" => 'highest'}),
409 409 :method => method, :title => l(:label_sort_highest)) +
410 410 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
411 411 url.merge({"#{name}[move_to]" => 'higher'}),
412 412 :method => method, :title => l(:label_sort_higher)) +
413 413 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
414 414 url.merge({"#{name}[move_to]" => 'lower'}),
415 415 :method => method, :title => l(:label_sort_lower)) +
416 416 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
417 417 url.merge({"#{name}[move_to]" => 'lowest'}),
418 418 :method => method, :title => l(:label_sort_lowest))
419 419 end
420 420
421 421 def breadcrumb(*args)
422 422 elements = args.flatten
423 423 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
424 424 end
425 425
426 426 def other_formats_links(&block)
427 427 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
428 428 yield Redmine::Views::OtherFormatsBuilder.new(self)
429 429 concat('</p>'.html_safe)
430 430 end
431 431
432 432 def page_header_title
433 433 if @project.nil? || @project.new_record?
434 434 h(Setting.app_title)
435 435 else
436 436 b = []
437 437 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
438 438 if ancestors.any?
439 439 root = ancestors.shift
440 440 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
441 441 if ancestors.size > 2
442 442 b << "\xe2\x80\xa6"
443 443 ancestors = ancestors[-2, 2]
444 444 end
445 445 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
446 446 end
447 447 b << h(@project)
448 448 b.join(" \xc2\xbb ").html_safe
449 449 end
450 450 end
451 451
452 452 # Returns a h2 tag and sets the html title with the given arguments
453 453 def title(*args)
454 454 strings = args.map do |arg|
455 455 if arg.is_a?(Array) && arg.size >= 2
456 456 link_to(*arg)
457 457 else
458 458 h(arg.to_s)
459 459 end
460 460 end
461 461 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
462 462 content_tag('h2', strings.join(' &#187; ').html_safe)
463 463 end
464 464
465 465 # Sets the html title
466 466 # Returns the html title when called without arguments
467 467 # Current project name and app_title and automatically appended
468 468 # Exemples:
469 469 # html_title 'Foo', 'Bar'
470 470 # html_title # => 'Foo - Bar - My Project - Redmine'
471 471 def html_title(*args)
472 472 if args.empty?
473 473 title = @html_title || []
474 474 title << @project.name if @project
475 475 title << Setting.app_title unless Setting.app_title == title.last
476 476 title.reject(&:blank?).join(' - ')
477 477 else
478 478 @html_title ||= []
479 479 @html_title += args
480 480 end
481 481 end
482 482
483 483 # Returns the theme, controller name, and action as css classes for the
484 484 # HTML body.
485 485 def body_css_classes
486 486 css = []
487 487 if theme = Redmine::Themes.theme(Setting.ui_theme)
488 488 css << 'theme-' + theme.name
489 489 end
490 490
491 491 css << 'controller-' + controller_name
492 492 css << 'action-' + action_name
493 493 css.join(' ')
494 494 end
495 495
496 496 def accesskey(s)
497 497 @used_accesskeys ||= []
498 498 key = Redmine::AccessKeys.key_for(s)
499 499 return nil if @used_accesskeys.include?(key)
500 500 @used_accesskeys << key
501 501 key
502 502 end
503 503
504 504 # Formats text according to system settings.
505 505 # 2 ways to call this method:
506 506 # * with a String: textilizable(text, options)
507 507 # * with an object and one of its attribute: textilizable(issue, :description, options)
508 508 def textilizable(*args)
509 509 options = args.last.is_a?(Hash) ? args.pop : {}
510 510 case args.size
511 511 when 1
512 512 obj = options[:object]
513 513 text = args.shift
514 514 when 2
515 515 obj = args.shift
516 516 attr = args.shift
517 517 text = obj.send(attr).to_s
518 518 else
519 519 raise ArgumentError, 'invalid arguments to textilizable'
520 520 end
521 521 return '' if text.blank?
522 522 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
523 523 only_path = options.delete(:only_path) == false ? false : true
524 524
525 525 text = text.dup
526 526 macros = catch_macros(text)
527 527 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
528 528
529 529 @parsed_headings = []
530 530 @heading_anchors = {}
531 531 @current_section = 0 if options[:edit_section_links]
532 532
533 533 parse_sections(text, project, obj, attr, only_path, options)
534 534 text = parse_non_pre_blocks(text, obj, macros) do |text|
535 535 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
536 536 send method_name, text, project, obj, attr, only_path, options
537 537 end
538 538 end
539 539 parse_headings(text, project, obj, attr, only_path, options)
540 540
541 541 if @parsed_headings.any?
542 542 replace_toc(text, @parsed_headings)
543 543 end
544 544
545 545 text.html_safe
546 546 end
547 547
548 548 def parse_non_pre_blocks(text, obj, macros)
549 549 s = StringScanner.new(text)
550 550 tags = []
551 551 parsed = ''
552 552 while !s.eos?
553 553 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
554 554 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
555 555 if tags.empty?
556 556 yield text
557 557 inject_macros(text, obj, macros) if macros.any?
558 558 else
559 559 inject_macros(text, obj, macros, false) if macros.any?
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 attachments = options[:attachments] || []
583 583 attachments += obj.attachments if obj.respond_to?(:attachments)
584 584 if attachments.present?
585 585 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
586 586 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
587 587 # search for the picture in attachments
588 588 if found = Attachment.latest_attach(attachments, filename)
589 589 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
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 identifier, page = $1, $2
619 619 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
620 620 title ||= identifier 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 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
641 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
642 642 :id => wiki_page_id, :version => nil, :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 # Projects:
683 683 # project:someproject -> Link to project named "someproject"
684 684 # project#3 -> Link to project with id 3
685 685 #
686 686 # Links can refer other objects from other projects, using project identifier:
687 687 # identifier:r52
688 688 # identifier:document:"Some document"
689 689 # identifier:version:1.0.0
690 690 # identifier:source:some/file
691 691 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
692 692 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|
693 693 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
694 694 link = nil
695 695 project = default_project
696 696 if project_identifier
697 697 project = Project.visible.find_by_identifier(project_identifier)
698 698 end
699 699 if esc.nil?
700 700 if prefix.nil? && sep == 'r'
701 701 if project
702 702 repository = nil
703 703 if repo_identifier
704 704 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
705 705 else
706 706 repository = project.repository
707 707 end
708 708 # project.changesets.visible raises an SQL error because of a double join on repositories
709 709 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
710 710 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},
711 711 :class => 'changeset',
712 712 :title => truncate_single_line(changeset.comments, :length => 100))
713 713 end
714 714 end
715 715 elsif sep == '#'
716 716 oid = identifier.to_i
717 717 case prefix
718 718 when nil
719 719 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
720 720 anchor = comment_id ? "note-#{comment_id}" : nil
721 721 link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
722 722 :class => issue.css_classes,
723 723 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
724 724 end
725 725 when 'document'
726 726 if document = Document.visible.find_by_id(oid)
727 727 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
728 728 :class => 'document'
729 729 end
730 730 when 'version'
731 731 if version = Version.visible.find_by_id(oid)
732 732 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
733 733 :class => 'version'
734 734 end
735 735 when 'message'
736 736 if message = Message.visible.find_by_id(oid, :include => :parent)
737 737 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
738 738 end
739 739 when 'forum'
740 740 if board = Board.visible.find_by_id(oid)
741 741 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
742 742 :class => 'board'
743 743 end
744 744 when 'news'
745 745 if news = News.visible.find_by_id(oid)
746 746 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
747 747 :class => 'news'
748 748 end
749 749 when 'project'
750 750 if p = Project.visible.find_by_id(oid)
751 751 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
752 752 end
753 753 end
754 754 elsif sep == ':'
755 755 # removes the double quotes if any
756 756 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
757 757 case prefix
758 758 when 'document'
759 759 if project && document = project.documents.visible.find_by_title(name)
760 760 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
761 761 :class => 'document'
762 762 end
763 763 when 'version'
764 764 if project && version = project.versions.visible.find_by_name(name)
765 765 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
766 766 :class => 'version'
767 767 end
768 768 when 'forum'
769 769 if project && board = project.boards.visible.find_by_name(name)
770 770 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
771 771 :class => 'board'
772 772 end
773 773 when 'news'
774 774 if project && news = project.news.visible.find_by_title(name)
775 775 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
776 776 :class => 'news'
777 777 end
778 778 when 'commit', 'source', 'export'
779 779 if project
780 780 repository = nil
781 781 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
782 782 repo_prefix, repo_identifier, name = $1, $2, $3
783 783 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
784 784 else
785 785 repository = project.repository
786 786 end
787 787 if prefix == 'commit'
788 788 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
789 789 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},
790 790 :class => 'changeset',
791 791 :title => truncate_single_line(changeset.comments, :length => 100)
792 792 end
793 793 else
794 794 if repository && User.current.allowed_to?(:browse_repository, project)
795 795 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
796 796 path, rev, anchor = $1, $3, $5
797 797 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
798 798 :path => to_path_param(path),
799 799 :rev => rev,
800 800 :anchor => anchor},
801 801 :class => (prefix == 'export' ? 'source download' : 'source')
802 802 end
803 803 end
804 804 repo_prefix = nil
805 805 end
806 806 when 'attachment'
807 807 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
808 808 if attachments && attachment = Attachment.latest_attach(attachments, name)
809 809 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
810 810 end
811 811 when 'project'
812 812 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
813 813 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
814 814 end
815 815 end
816 816 end
817 817 end
818 818 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
819 819 end
820 820 end
821 821
822 822 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
823 823
824 824 def parse_sections(text, project, obj, attr, only_path, options)
825 825 return unless options[:edit_section_links]
826 826 text.gsub!(HEADING_RE) do
827 827 heading = $1
828 828 @current_section += 1
829 829 if @current_section > 1
830 830 content_tag('div',
831 831 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
832 832 :class => 'contextual',
833 833 :title => l(:button_edit_section)) + heading.html_safe
834 834 else
835 835 heading
836 836 end
837 837 end
838 838 end
839 839
840 840 # Headings and TOC
841 841 # Adds ids and links to headings unless options[:headings] is set to false
842 842 def parse_headings(text, project, obj, attr, only_path, options)
843 843 return if options[:headings] == false
844 844
845 845 text.gsub!(HEADING_RE) do
846 846 level, attrs, content = $2.to_i, $3, $4
847 847 item = strip_tags(content).strip
848 848 anchor = sanitize_anchor_name(item)
849 849 # used for single-file wiki export
850 850 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
851 851 @heading_anchors[anchor] ||= 0
852 852 idx = (@heading_anchors[anchor] += 1)
853 853 if idx > 1
854 854 anchor = "#{anchor}-#{idx}"
855 855 end
856 856 @parsed_headings << [level, anchor, item]
857 857 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
858 858 end
859 859 end
860 860
861 861 MACROS_RE = /(
862 862 (!)? # escaping
863 863 (
864 864 \{\{ # opening tag
865 865 ([\w]+) # macro name
866 866 (\(([^\n\r]*?)\))? # optional arguments
867 867 ([\n\r].*?[\n\r])? # optional block of text
868 868 \}\} # closing tag
869 869 )
870 870 )/mx unless const_defined?(:MACROS_RE)
871 871
872 872 MACRO_SUB_RE = /(
873 873 \{\{
874 874 macro\((\d+)\)
875 875 \}\}
876 876 )/x unless const_defined?(:MACRO_SUB_RE)
877 877
878 878 # Extracts macros from text
879 879 def catch_macros(text)
880 880 macros = {}
881 881 text.gsub!(MACROS_RE) do
882 882 all, macro = $1, $4.downcase
883 883 if macro_exists?(macro) || all =~ MACRO_SUB_RE
884 884 index = macros.size
885 885 macros[index] = all
886 886 "{{macro(#{index})}}"
887 887 else
888 888 all
889 889 end
890 890 end
891 891 macros
892 892 end
893 893
894 894 # Executes and replaces macros in text
895 895 def inject_macros(text, obj, macros, execute=true)
896 896 text.gsub!(MACRO_SUB_RE) do
897 897 all, index = $1, $2.to_i
898 898 orig = macros.delete(index)
899 899 if execute && orig && orig =~ MACROS_RE
900 900 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
901 901 if esc.nil?
902 902 h(exec_macro(macro, obj, args, block) || all)
903 903 else
904 904 h(all)
905 905 end
906 906 elsif orig
907 907 h(orig)
908 908 else
909 909 h(all)
910 910 end
911 911 end
912 912 end
913 913
914 914 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
915 915
916 916 # Renders the TOC with given headings
917 917 def replace_toc(text, headings)
918 918 text.gsub!(TOC_RE) do
919 919 # Keep only the 4 first levels
920 920 headings = headings.select{|level, anchor, item| level <= 4}
921 921 if headings.empty?
922 922 ''
923 923 else
924 924 div_class = 'toc'
925 925 div_class << ' right' if $1 == '>'
926 926 div_class << ' left' if $1 == '<'
927 927 out = "<ul class=\"#{div_class}\"><li>"
928 928 root = headings.map(&:first).min
929 929 current = root
930 930 started = false
931 931 headings.each do |level, anchor, item|
932 932 if level > current
933 933 out << '<ul><li>' * (level - current)
934 934 elsif level < current
935 935 out << "</li></ul>\n" * (current - level) + "</li><li>"
936 936 elsif started
937 937 out << '</li><li>'
938 938 end
939 939 out << "<a href=\"##{anchor}\">#{item}</a>"
940 940 current = level
941 941 started = true
942 942 end
943 943 out << '</li></ul>' * (current - root)
944 944 out << '</li></ul>'
945 945 end
946 946 end
947 947 end
948 948
949 949 # Same as Rails' simple_format helper without using paragraphs
950 950 def simple_format_without_paragraph(text)
951 951 text.to_s.
952 952 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
953 953 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
954 954 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
955 955 html_safe
956 956 end
957 957
958 958 def lang_options_for_select(blank=true)
959 959 (blank ? [["(auto)", ""]] : []) + languages_options
960 960 end
961 961
962 962 def label_tag_for(name, option_tags = nil, options = {})
963 963 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
964 964 content_tag("label", label_text)
965 965 end
966 966
967 967 def labelled_form_for(*args, &proc)
968 968 args << {} unless args.last.is_a?(Hash)
969 969 options = args.last
970 970 if args.first.is_a?(Symbol)
971 971 options.merge!(:as => args.shift)
972 972 end
973 973 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
974 974 form_for(*args, &proc)
975 975 end
976 976
977 977 def labelled_fields_for(*args, &proc)
978 978 args << {} unless args.last.is_a?(Hash)
979 979 options = args.last
980 980 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
981 981 fields_for(*args, &proc)
982 982 end
983 983
984 984 def labelled_remote_form_for(*args, &proc)
985 985 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
986 986 args << {} unless args.last.is_a?(Hash)
987 987 options = args.last
988 988 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
989 989 form_for(*args, &proc)
990 990 end
991 991
992 992 def error_messages_for(*objects)
993 993 html = ""
994 994 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
995 995 errors = objects.map {|o| o.errors.full_messages}.flatten
996 996 if errors.any?
997 997 html << "<div id='errorExplanation'><ul>\n"
998 998 errors.each do |error|
999 999 html << "<li>#{h error}</li>\n"
1000 1000 end
1001 1001 html << "</ul></div>\n"
1002 1002 end
1003 1003 html.html_safe
1004 end
1004 end
1005 1005
1006 1006 def delete_link(url, options={})
1007 1007 options = {
1008 1008 :method => :delete,
1009 1009 :data => {:confirm => l(:text_are_you_sure)},
1010 1010 :class => 'icon icon-del'
1011 1011 }.merge(options)
1012 1012
1013 1013 link_to l(:button_delete), url, options
1014 1014 end
1015 1015
1016 1016 def preview_link(url, form, target='preview', options={})
1017 1017 content_tag 'a', l(:label_preview), {
1018 :href => "#",
1019 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1018 :href => "#",
1019 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1020 1020 :accesskey => accesskey(:preview)
1021 1021 }.merge(options)
1022 1022 end
1023 1023
1024 1024 def link_to_function(name, function, html_options={})
1025 1025 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1026 1026 end
1027 1027
1028 1028 # Helper to render JSON in views
1029 1029 def raw_json(arg)
1030 1030 arg.to_json.to_s.gsub('/', '\/').html_safe
1031 1031 end
1032 1032
1033 1033 def back_url
1034 1034 url = params[:back_url]
1035 1035 if url.nil? && referer = request.env['HTTP_REFERER']
1036 1036 url = CGI.unescape(referer.to_s)
1037 1037 end
1038 1038 url
1039 1039 end
1040 1040
1041 1041 def back_url_hidden_field_tag
1042 1042 url = back_url
1043 1043 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1044 1044 end
1045 1045
1046 1046 def check_all_links(form_name)
1047 1047 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1048 1048 " | ".html_safe +
1049 1049 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1050 1050 end
1051 1051
1052 1052 def progress_bar(pcts, options={})
1053 1053 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1054 1054 pcts = pcts.collect(&:round)
1055 1055 pcts[1] = pcts[1] - pcts[0]
1056 1056 pcts << (100 - pcts[1] - pcts[0])
1057 1057 width = options[:width] || '100px;'
1058 1058 legend = options[:legend] || ''
1059 1059 content_tag('table',
1060 1060 content_tag('tr',
1061 1061 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1062 1062 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1063 1063 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1064 1064 ), :class => 'progress', :style => "width: #{width};").html_safe +
1065 1065 content_tag('p', legend, :class => 'percent').html_safe
1066 1066 end
1067 1067
1068 1068 def checked_image(checked=true)
1069 1069 if checked
1070 1070 image_tag 'toggle_check.png'
1071 1071 end
1072 1072 end
1073 1073
1074 1074 def context_menu(url)
1075 1075 unless @context_menu_included
1076 1076 content_for :header_tags do
1077 1077 javascript_include_tag('context_menu') +
1078 1078 stylesheet_link_tag('context_menu')
1079 1079 end
1080 1080 if l(:direction) == 'rtl'
1081 1081 content_for :header_tags do
1082 1082 stylesheet_link_tag('context_menu_rtl')
1083 1083 end
1084 1084 end
1085 1085 @context_menu_included = true
1086 1086 end
1087 1087 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1088 1088 end
1089 1089
1090 1090 def calendar_for(field_id)
1091 1091 include_calendar_headers_tags
1092 1092 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1093 1093 end
1094 1094
1095 1095 def include_calendar_headers_tags
1096 1096 unless @calendar_headers_tags_included
1097 1097 tags = javascript_include_tag("datepicker")
1098 1098 @calendar_headers_tags_included = true
1099 1099 content_for :header_tags do
1100 1100 start_of_week = Setting.start_of_week
1101 1101 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1102 1102 # Redmine uses 1..7 (monday..sunday) in settings and locales
1103 1103 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1104 1104 start_of_week = start_of_week.to_i % 7
1105 1105 tags << javascript_tag(
1106 1106 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1107 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1107 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1108 1108 path_to_image('/images/calendar.png') +
1109 1109 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1110 1110 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1111 1111 "beforeShow: beforeShowDatePicker};")
1112 1112 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1113 1113 unless jquery_locale == 'en'
1114 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1114 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1115 1115 end
1116 1116 tags
1117 1117 end
1118 1118 end
1119 1119 end
1120 1120
1121 1121 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1122 1122 # Examples:
1123 1123 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1124 1124 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1125 1125 #
1126 1126 def stylesheet_link_tag(*sources)
1127 1127 options = sources.last.is_a?(Hash) ? sources.pop : {}
1128 1128 plugin = options.delete(:plugin)
1129 1129 sources = sources.map do |source|
1130 1130 if plugin
1131 1131 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1132 1132 elsif current_theme && current_theme.stylesheets.include?(source)
1133 1133 current_theme.stylesheet_path(source)
1134 1134 else
1135 1135 source
1136 1136 end
1137 1137 end
1138 1138 super sources, options
1139 1139 end
1140 1140
1141 1141 # Overrides Rails' image_tag with themes and plugins support.
1142 1142 # Examples:
1143 1143 # image_tag('image.png') # => picks image.png from the current theme or defaults
1144 1144 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1145 1145 #
1146 1146 def image_tag(source, options={})
1147 1147 if plugin = options.delete(:plugin)
1148 1148 source = "/plugin_assets/#{plugin}/images/#{source}"
1149 1149 elsif current_theme && current_theme.images.include?(source)
1150 1150 source = current_theme.image_path(source)
1151 1151 end
1152 1152 super source, options
1153 1153 end
1154 1154
1155 1155 # Overrides Rails' javascript_include_tag with plugins support
1156 1156 # Examples:
1157 1157 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1158 1158 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1159 1159 #
1160 1160 def javascript_include_tag(*sources)
1161 1161 options = sources.last.is_a?(Hash) ? sources.pop : {}
1162 1162 if plugin = options.delete(:plugin)
1163 1163 sources = sources.map do |source|
1164 1164 if plugin
1165 1165 "/plugin_assets/#{plugin}/javascripts/#{source}"
1166 1166 else
1167 1167 source
1168 1168 end
1169 1169 end
1170 1170 end
1171 1171 super sources, options
1172 1172 end
1173 1173
1174 1174 def content_for(name, content = nil, &block)
1175 1175 @has_content ||= {}
1176 1176 @has_content[name] = true
1177 1177 super(name, content, &block)
1178 1178 end
1179 1179
1180 1180 def has_content?(name)
1181 1181 (@has_content && @has_content[name]) || false
1182 1182 end
1183 1183
1184 1184 def sidebar_content?
1185 1185 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1186 1186 end
1187 1187
1188 1188 def view_layouts_base_sidebar_hook_response
1189 1189 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1190 1190 end
1191 1191
1192 1192 def email_delivery_enabled?
1193 1193 !!ActionMailer::Base.perform_deliveries
1194 1194 end
1195 1195
1196 1196 # Returns the avatar image tag for the given +user+ if avatars are enabled
1197 1197 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1198 1198 def avatar(user, options = { })
1199 1199 if Setting.gravatar_enabled?
1200 1200 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1201 1201 email = nil
1202 1202 if user.respond_to?(:mail)
1203 1203 email = user.mail
1204 1204 elsif user.to_s =~ %r{<(.+?)>}
1205 1205 email = $1
1206 1206 end
1207 1207 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1208 1208 else
1209 1209 ''
1210 1210 end
1211 1211 end
1212 1212
1213 1213 def sanitize_anchor_name(anchor)
1214 1214 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1215 1215 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1216 1216 else
1217 1217 # TODO: remove when ruby1.8 is no longer supported
1218 1218 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1219 1219 end
1220 1220 end
1221 1221
1222 1222 # Returns the javascript tags that are included in the html layout head
1223 1223 def javascript_heads
1224 1224 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1225 1225 unless User.current.pref.warn_on_leaving_unsaved == '0'
1226 1226 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1227 1227 end
1228 1228 tags
1229 1229 end
1230 1230
1231 1231 def favicon
1232 1232 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1233 1233 end
1234 1234
1235 1235 def robot_exclusion_tag
1236 1236 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1237 1237 end
1238 1238
1239 1239 # Returns true if arg is expected in the API response
1240 1240 def include_in_api_response?(arg)
1241 1241 unless @included_in_api_response
1242 1242 param = params[:include]
1243 1243 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1244 1244 @included_in_api_response.collect!(&:strip)
1245 1245 end
1246 1246 @included_in_api_response.include?(arg.to_s)
1247 1247 end
1248 1248
1249 1249 # Returns options or nil if nometa param or X-Redmine-Nometa header
1250 1250 # was set in the request
1251 1251 def api_meta(options)
1252 1252 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1253 1253 # compatibility mode for activeresource clients that raise
1254 1254 # an error when unserializing an array with attributes
1255 1255 nil
1256 1256 else
1257 1257 options
1258 1258 end
1259 1259 end
1260 1260
1261 1261 private
1262 1262
1263 1263 def wiki_helper
1264 1264 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1265 1265 extend helper
1266 1266 return self
1267 1267 end
1268 1268
1269 1269 def link_to_content_update(text, url_params = {}, html_options = {})
1270 1270 link_to(text, url_params, html_options)
1271 1271 end
1272 1272 end
General Comments 0
You need to be logged in to leave comments. Login now