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