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