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