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