Mercurial > hg > soundsoftware-site
comparison app/helpers/application_helper.rb @ 1339:c03a6c3c4db9 luisf
Merge
author | luisf <luis.figueira@eecs.qmul.ac.uk> |
---|---|
date | Thu, 20 Jun 2013 14:34:42 +0100 |
parents | 0a574315af3e |
children | 4f746d8966dd 51364c0cd58f |
comparison
equal
deleted
inserted
replaced
1079:413d1d9c3efa | 1339:c03a6c3c4db9 |
---|---|
1 # encoding: utf-8 | 1 # encoding: utf-8 |
2 # | 2 # |
3 # Redmine - project management software | 3 # Redmine - project management software |
4 # Copyright (C) 2006-2011 Jean-Philippe Lang | 4 # Copyright (C) 2006-2012 Jean-Philippe Lang |
5 # | 5 # |
6 # This program is free software; you can redistribute it and/or | 6 # This program is free software; you can redistribute it and/or |
7 # modify it under the terms of the GNU General Public License | 7 # modify it under the terms of the GNU General Public License |
8 # as published by the Free Software Foundation; either version 2 | 8 # as published by the Free Software Foundation; either version 2 |
9 # of the License, or (at your option) any later version. | 9 # of the License, or (at your option) any later version. |
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to | 41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to |
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) | 42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) |
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) | 43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) |
44 end | 44 end |
45 | 45 |
46 # Display a link to remote if user is authorized | |
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil) | |
48 url = options[:url] || {} | |
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action]) | |
50 end | |
51 | |
52 # Displays a link to user's account page if active | 46 # Displays a link to user's account page if active |
53 def link_to_user(user, options={}) | 47 def link_to_user(user, options={}) |
54 if user.is_a?(User) | 48 if user.is_a?(User) |
55 name = h(user.name(options[:format])) | 49 name = h(user.name(options[:format])) |
56 if user.active? | 50 if user.active? || (User.current.admin? && user.logged?) |
57 link_to(name, :controller => 'users', :action => 'show', :id => user) | 51 link_to name, user_path(user), :class => user.css_classes |
58 else | 52 else |
59 name | 53 name |
60 end | 54 end |
61 else | 55 else |
62 h(user.to_s) | 56 h(user.to_s) |
68 # | 62 # |
69 # link_to_issue(issue) # => Defect #6: This is the subject | 63 # link_to_issue(issue) # => Defect #6: This is the subject |
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... | 64 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... |
71 # link_to_issue(issue, :subject => false) # => Defect #6 | 65 # link_to_issue(issue, :subject => false) # => Defect #6 |
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6 | 66 # link_to_issue(issue, :project => true) # => Foo - Defect #6 |
67 # link_to_issue(issue, :subject => false, :tracker => false) # => #6 | |
73 # | 68 # |
74 def link_to_issue(issue, options={}) | 69 def link_to_issue(issue, options={}) |
75 title = nil | 70 title = nil |
76 subject = nil | 71 subject = nil |
72 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" | |
77 if options[:subject] == false | 73 if options[:subject] == false |
78 title = truncate(issue.subject, :length => 60) | 74 title = truncate(issue.subject, :length => 60) |
79 else | 75 else |
80 subject = issue.subject | 76 subject = issue.subject |
81 if options[:truncate] | 77 if options[:truncate] |
82 subject = truncate(subject, :length => options[:truncate]) | 78 subject = truncate(subject, :length => options[:truncate]) |
83 end | 79 end |
84 end | 80 end |
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, | 81 s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title |
86 :class => issue.css_classes, | 82 s << h(": #{subject}") if subject |
87 :title => title | 83 s = h("#{issue.project} - ") + s if options[:project] |
88 s << ": #{h subject}" if subject | |
89 s = "#{h issue.project} - " + s if options[:project] | |
90 s | 84 s |
91 end | 85 end |
92 | 86 |
93 # Generates a link to an attachment. | 87 # Generates a link to an attachment. |
94 # Options: | 88 # Options: |
95 # * :text - Link text (default to attachment filename) | 89 # * :text - Link text (default to attachment filename) |
96 # * :download - Force download (default: false) | 90 # * :download - Force download (default: false) |
97 def link_to_attachment(attachment, options={}) | 91 def link_to_attachment(attachment, options={}) |
98 text = options.delete(:text) || attachment.filename | 92 text = options.delete(:text) || attachment.filename |
99 action = options.delete(:download) ? 'download' : 'show' | 93 action = options.delete(:download) ? 'download' : 'show' |
94 opt_only_path = {} | |
95 opt_only_path[:only_path] = (options[:only_path] == false ? false : true) | |
96 options.delete(:only_path) | |
100 link_to(h(text), | 97 link_to(h(text), |
101 {:controller => 'attachments', :action => action, | 98 {:controller => 'attachments', :action => action, |
102 :id => attachment, :filename => attachment.filename }, | 99 :id => attachment, :filename => attachment.filename}.merge(opt_only_path), |
103 options) | 100 options) |
104 end | 101 end |
105 | 102 |
106 # Generates a link to a SCM revision | 103 # Generates a link to a SCM revision |
107 # Options: | 104 # Options: |
108 # * :text - Link text (default to the formatted revision) | 105 # * :text - Link text (default to the formatted revision) |
109 def link_to_revision(revision, project, options={}) | 106 def link_to_revision(revision, repository, options={}) |
107 if repository.is_a?(Project) | |
108 repository = repository.repository | |
109 end | |
110 text = options.delete(:text) || format_revision(revision) | 110 text = options.delete(:text) || format_revision(revision) |
111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision | 111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision |
112 | 112 link_to( |
113 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev}, | 113 h(text), |
114 :title => l(:label_revision_id, format_revision(revision))) | 114 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, |
115 :title => l(:label_revision_id, format_revision(revision)) | |
116 ) | |
115 end | 117 end |
116 | 118 |
117 # Generates a link to a message | 119 # Generates a link to a message |
118 def link_to_message(message, options={}, html_options = nil) | 120 def link_to_message(message, options={}, html_options = nil) |
119 link_to( | 121 link_to( |
120 h(truncate(message.subject, :length => 60)), | 122 h(truncate(message.subject, :length => 60)), |
121 { :controller => 'messages', :action => 'show', | 123 { :controller => 'messages', :action => 'show', |
122 :board_id => message.board_id, | 124 :board_id => message.board_id, |
123 :id => message.root, | 125 :id => (message.parent_id || message.id), |
124 :r => (message.parent_id && message.id), | 126 :r => (message.parent_id && message.id), |
125 :anchor => (message.parent_id ? "message-#{message.id}" : nil) | 127 :anchor => (message.parent_id ? "message-#{message.id}" : nil) |
126 }.merge(options), | 128 }.merge(options), |
127 html_options | 129 html_options |
128 ) | 130 ) |
135 # link_to_project(project, :action=>'settings') # => link to project settings | 137 # link_to_project(project, :action=>'settings') # => link to project settings |
136 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options | 138 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options |
137 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) | 139 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) |
138 # | 140 # |
139 def link_to_project(project, options={}, html_options = nil) | 141 def link_to_project(project, options={}, html_options = nil) |
140 if project.active? | 142 if project.archived? |
143 h(project) | |
144 else | |
141 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) | 145 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) |
142 link_to(h(project), url, html_options) | 146 link_to(h(project), url, html_options) |
143 else | 147 end |
144 h(project) | 148 end |
145 end | 149 |
150 def wiki_page_path(page, options={}) | |
151 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options)) | |
152 end | |
153 | |
154 def thumbnail_tag(attachment) | |
155 link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)), | |
156 {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename}, | |
157 :title => attachment.filename | |
146 end | 158 end |
147 | 159 |
148 def toggle_link(name, id, options={}) | 160 def toggle_link(name, id, options={}) |
149 onclick = "Element.toggle('#{id}'); " | 161 onclick = "$('##{id}').toggle(); " |
150 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ") | 162 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") |
151 onclick << "return false;" | 163 onclick << "return false;" |
152 link_to(name, "#", :onclick => onclick) | 164 link_to(name, "#", :onclick => onclick) |
153 end | 165 end |
154 | 166 |
155 def image_to_function(name, function, html_options = {}) | 167 def image_to_function(name, function, html_options = {}) |
158 :type => "image", :src => image_path(name), | 170 :type => "image", :src => image_path(name), |
159 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" | 171 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" |
160 })) | 172 })) |
161 end | 173 end |
162 | 174 |
163 def prompt_to_remote(name, text, param, url, html_options = {}) | |
164 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;" | |
165 link_to name, {}, html_options | |
166 end | |
167 | |
168 def format_activity_title(text) | 175 def format_activity_title(text) |
169 h(truncate_single_line(text, :length => 100)) | 176 h(truncate_single_line(text, :length => 100)) |
170 end | 177 end |
171 | 178 |
172 def format_activity_day(date) | 179 def format_activity_day(date) |
173 date == Date.today ? l(:label_today).titleize : format_date(date) | 180 date == User.current.today ? l(:label_today).titleize : format_date(date) |
174 end | 181 end |
175 | 182 |
176 def format_activity_description(text) | 183 def format_activity_description(text) |
177 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />") | 184 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') |
185 ).gsub(/[\r\n]+/, "<br />").html_safe | |
178 end | 186 end |
179 | 187 |
180 def format_version_name(version) | 188 def format_version_name(version) |
181 if version.project == @project | 189 if version.project == @project |
182 h(version) | 190 h(version) |
189 if date | 197 if date |
190 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) | 198 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) |
191 end | 199 end |
192 end | 200 end |
193 | 201 |
194 def render_page_hierarchy(pages, node=nil, options={}) | 202 # Renders a tree of projects as a nested set of unordered lists |
195 content = '' | 203 # The given collection may be a subset of the whole project tree |
196 if pages[node] | 204 # (eg. some intermediate nodes are private and can not be seen) |
197 content << "<ul class=\"pages-hierarchy\">\n" | 205 def render_project_nested_lists(projects) |
198 pages[node].each do |page| | |
199 content << "<li>" | |
200 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}, | |
201 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) | |
202 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] | |
203 content << "</li>\n" | |
204 end | |
205 content << "</ul>\n" | |
206 end | |
207 content.html_safe | |
208 end | |
209 | |
210 # Renders flash messages | |
211 def render_flash_messages | |
212 s = '' | |
213 flash.each do |k,v| | |
214 s << content_tag('div', v, :class => "flash #{k}") | |
215 end | |
216 s.html_safe | |
217 end | |
218 | |
219 # Renders tabs and their content | |
220 def render_tabs(tabs) | |
221 if tabs.any? | |
222 render :partial => 'common/tabs', :locals => {:tabs => tabs} | |
223 else | |
224 content_tag 'p', l(:label_no_data), :class => "nodata" | |
225 end | |
226 end | |
227 | |
228 # Renders the project quick-jump box | |
229 def render_project_jump_box | |
230 return unless User.current.logged? | |
231 projects = User.current.memberships.collect(&:project).compact.uniq | |
232 if projects.any? | |
233 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' + | |
234 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" + | |
235 '<option value="" disabled="disabled">---</option>' | |
236 s << project_tree_options_for_select(projects, :selected => @project) do |p| | |
237 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) } | |
238 end | |
239 s << '</select>' | |
240 s.html_safe | |
241 end | |
242 end | |
243 | |
244 def project_tree_options_for_select(projects, options = {}) | |
245 s = '' | |
246 project_tree(projects) do |project, level| | |
247 name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '') | |
248 tag_options = {:value => project.id} | |
249 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) | |
250 tag_options[:selected] = 'selected' | |
251 else | |
252 tag_options[:selected] = nil | |
253 end | |
254 tag_options.merge!(yield(project)) if block_given? | |
255 s << content_tag('option', name_prefix + h(project), tag_options) | |
256 end | |
257 s.html_safe | |
258 end | |
259 | |
260 # Yields the given block for each project with its level in the tree | |
261 # | |
262 # Wrapper for Project#project_tree | |
263 def project_tree(projects, &block) | |
264 Project.project_tree(projects, &block) | |
265 end | |
266 | |
267 def project_nested_ul(projects, &block) | |
268 s = '' | 206 s = '' |
269 if projects.any? | 207 if projects.any? |
270 ancestors = [] | 208 ancestors = [] |
209 original_project = @project | |
271 projects.sort_by(&:lft).each do |project| | 210 projects.sort_by(&:lft).each do |project| |
211 # set the project environment to please macros. | |
212 @project = project | |
272 if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) | 213 if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) |
273 s << "<ul>\n" | 214 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n" |
274 else | 215 else |
275 ancestors.pop | 216 ancestors.pop |
276 s << "</li>" | 217 s << "</li>" |
277 while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) | 218 while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) |
278 ancestors.pop | 219 ancestors.pop |
279 s << "</ul></li>\n" | 220 s << "</ul></li>\n" |
280 end | 221 end |
281 end | 222 end |
282 s << "<li>" | 223 classes = (ancestors.empty? ? 'root' : 'child') |
283 s << yield(project).to_s | 224 s << "<li class='#{classes}'><div class='#{classes}'>" |
225 s << h(block_given? ? yield(project) : project.name) | |
226 s << "</div>\n" | |
284 ancestors << project | 227 ancestors << project |
285 end | 228 end |
286 s << ("</li></ul>\n" * ancestors.size) | 229 s << ("</li></ul>\n" * ancestors.size) |
230 @project = original_project | |
287 end | 231 end |
288 s.html_safe | 232 s.html_safe |
233 end | |
234 | |
235 def render_page_hierarchy(pages, node=nil, options={}) | |
236 content = '' | |
237 if pages[node] | |
238 content << "<ul class=\"pages-hierarchy\">\n" | |
239 pages[node].each do |page| | |
240 content << "<li>" | |
241 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil}, | |
242 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) | |
243 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] | |
244 content << "</li>\n" | |
245 end | |
246 content << "</ul>\n" | |
247 end | |
248 content.html_safe | |
249 end | |
250 | |
251 # Renders flash messages | |
252 def render_flash_messages | |
253 s = '' | |
254 flash.each do |k,v| | |
255 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") | |
256 end | |
257 s.html_safe | |
258 end | |
259 | |
260 # Renders tabs and their content | |
261 def render_tabs(tabs) | |
262 if tabs.any? | |
263 render :partial => 'common/tabs', :locals => {:tabs => tabs} | |
264 else | |
265 content_tag 'p', l(:label_no_data), :class => "nodata" | |
266 end | |
267 end | |
268 | |
269 # Renders the project quick-jump box | |
270 def render_project_jump_box | |
271 return unless User.current.logged? | |
272 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq | |
273 if projects.any? | |
274 options = | |
275 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" + | |
276 '<option value="" disabled="disabled">---</option>').html_safe | |
277 | |
278 options << project_tree_options_for_select(projects, :selected => @project) do |p| | |
279 { :value => project_path(:id => p, :jump => current_menu_item) } | |
280 end | |
281 | |
282 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') | |
283 end | |
284 end | |
285 | |
286 def project_tree_options_for_select(projects, options = {}) | |
287 s = '' | |
288 project_tree(projects) do |project, level| | |
289 name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe | |
290 tag_options = {:value => project.id} | |
291 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) | |
292 tag_options[:selected] = 'selected' | |
293 else | |
294 tag_options[:selected] = nil | |
295 end | |
296 tag_options.merge!(yield(project)) if block_given? | |
297 s << content_tag('option', name_prefix + h(project), tag_options) | |
298 end | |
299 s.html_safe | |
300 end | |
301 | |
302 # Yields the given block for each project with its level in the tree | |
303 # | |
304 # Wrapper for Project#project_tree | |
305 def project_tree(projects, &block) | |
306 Project.project_tree(projects, &block) | |
289 end | 307 end |
290 | 308 |
291 def principals_check_box_tags(name, principals) | 309 def principals_check_box_tags(name, principals) |
292 s = '' | 310 s = '' |
293 principals.sort.each do |principal| | 311 principals.sort.each do |principal| |
294 | 312 |
295 if principal.type == "User": | 313 if principal.type == "User" |
296 s << "<label>#{ check_box_tag name, principal.id, false } #{link_to_user principal}</label>\n" | 314 s << "<label>#{ check_box_tag name, principal.id, false } #{link_to_user principal}</label>\n" |
297 else | 315 else |
298 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal} (Group)</label>\n" | 316 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal} (Group)</label>\n" |
299 end | 317 end |
300 | 318 |
301 end | 319 end |
302 s.html_safe | 320 s.html_safe |
303 end | 321 end |
304 | 322 |
305 # Returns a string for users/groups option tags | 323 # Returns a string for users/groups option tags |
306 def principals_options_for_select(collection, selected=nil) | 324 def principals_options_for_select(collection, selected=nil) |
307 s = '' | 325 s = '' |
326 if collection.include?(User.current) | |
327 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) | |
328 end | |
308 groups = '' | 329 groups = '' |
309 collection.sort.each do |element| | 330 collection.sort.each do |element| |
310 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) | 331 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) |
311 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>) | 332 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>) |
312 end | 333 end |
313 unless groups.empty? | 334 unless groups.empty? |
314 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) | 335 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) |
315 end | 336 end |
316 s | 337 s.html_safe |
338 end | |
339 | |
340 # Options for the new membership projects combo-box | |
341 def options_for_membership_project_select(principal, projects) | |
342 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") | |
343 options << project_tree_options_for_select(projects) do |p| | |
344 {:disabled => principal.projects.include?(p)} | |
345 end | |
346 options | |
317 end | 347 end |
318 | 348 |
319 # Truncates and returns the string as a single line | 349 # Truncates and returns the string as a single line |
320 def truncate_single_line(string, *args) | 350 def truncate_single_line(string, *args) |
321 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') | 351 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') |
329 else | 359 else |
330 string | 360 string |
331 end | 361 end |
332 end | 362 end |
333 | 363 |
364 def anchor(text) | |
365 text.to_s.gsub(' ', '_') | |
366 end | |
367 | |
334 def html_hours(text) | 368 def html_hours(text) |
335 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe | 369 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe |
336 end | 370 end |
337 | 371 |
338 def authoring(created, author, options={}) | 372 def authoring(created, author, options={}) |
340 end | 374 end |
341 | 375 |
342 def time_tag(time) | 376 def time_tag(time) |
343 text = distance_of_time_in_words(Time.now, time) | 377 text = distance_of_time_in_words(Time.now, time) |
344 if @project | 378 if @project |
345 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time)) | 379 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) |
346 else | 380 else |
347 content_tag('acronym', text, :title => format_time(time)) | 381 content_tag('acronym', text, :title => format_time(time)) |
348 end | 382 end |
349 end | 383 end |
350 | 384 |
385 def syntax_highlight_lines(name, content) | |
386 lines = [] | |
387 syntax_highlight(name, content).each_line { |line| lines << line } | |
388 lines | |
389 end | |
390 | |
351 def syntax_highlight(name, content) | 391 def syntax_highlight(name, content) |
352 Redmine::SyntaxHighlighting.highlight_by_filename(content, name) | 392 Redmine::SyntaxHighlighting.highlight_by_filename(content, name) |
353 end | 393 end |
354 | 394 |
355 def to_path_param(path) | 395 def to_path_param(path) |
356 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?} | 396 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") |
397 str.blank? ? nil : str | |
357 end | 398 end |
358 | 399 |
359 def pagination_links_full(paginator, count=nil, options={}) | 400 def pagination_links_full(paginator, count=nil, options={}) |
360 page_param = options.delete(:page_param) || :page | 401 page_param = options.delete(:page_param) || :page |
361 per_page_links = options.delete(:per_page_links) | 402 per_page_links = options.delete(:per_page_links) |
380 url_param.merge(page_param => paginator.current.next)) | 421 url_param.merge(page_param => paginator.current.next)) |
381 end | 422 end |
382 | 423 |
383 unless count.nil? | 424 unless count.nil? |
384 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" | 425 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" |
385 if per_page_links != false && links = per_page_links(paginator.items_per_page) | 426 if per_page_links != false && links = per_page_links(paginator.items_per_page, count) |
386 html << " | #{links}" | 427 html << " | #{links}" |
387 end | 428 end |
388 end | 429 end |
389 | 430 |
390 html.html_safe | 431 html.html_safe |
391 end | 432 end |
392 | 433 |
393 def per_page_links(selected=nil) | 434 def per_page_links(selected=nil, item_count=nil) |
394 links = Setting.per_page_options_array.collect do |n| | 435 values = Setting.per_page_options_array |
436 if item_count && values.any? | |
437 if item_count > values.first | |
438 max = values.detect {|value| value >= item_count} || item_count | |
439 else | |
440 max = item_count | |
441 end | |
442 values = values.select {|value| value <= max || value == selected} | |
443 end | |
444 if values.empty? || (values.size == 1 && values.first == selected) | |
445 return nil | |
446 end | |
447 links = values.collect do |n| | |
395 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) | 448 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) |
396 end | 449 end |
397 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil | 450 l(:label_display_per_page, links.join(', ')) |
398 end | 451 end |
399 | 452 |
400 def reorder_links(name, url, method = :post) | 453 def reorder_links(name, url, method = :post) |
401 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), | 454 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), |
402 url.merge({"#{name}[move_to]" => 'highest'}), | 455 url.merge({"#{name}[move_to]" => 'highest'}), |
433 ancestors = (@project.root? ? [] : @project.ancestors.visible.all) | 486 ancestors = (@project.root? ? [] : @project.ancestors.visible.all) |
434 if ancestors.any? | 487 if ancestors.any? |
435 root = ancestors.shift | 488 root = ancestors.shift |
436 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') | 489 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') |
437 if ancestors.size > 2 | 490 if ancestors.size > 2 |
438 b << '…' | 491 b << '…' |
439 ancestors = ancestors[-2, 2] | 492 ancestors = ancestors[-2, 2] |
440 end | 493 end |
441 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } | 494 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } |
442 b = b.join(' » ') | 495 b = b.join(' » ').html_safe |
443 b << (' »') | 496 b << (' »'.html_safe) |
444 end | 497 end |
445 | 498 |
446 pname << h(@project) | 499 pname << h(@project) |
447 | 500 |
448 a = [pname, b] | 501 a = [pname, b] |
468 css = [] | 521 css = [] |
469 if theme = Redmine::Themes.theme(Setting.ui_theme) | 522 if theme = Redmine::Themes.theme(Setting.ui_theme) |
470 css << 'theme-' + theme.name | 523 css << 'theme-' + theme.name |
471 end | 524 end |
472 | 525 |
473 css << 'controller-' + params[:controller] | 526 css << 'controller-' + controller_name |
474 css << 'action-' + params[:action] | 527 css << 'action-' + action_name |
475 css.join(' ') | 528 css.join(' ') |
476 end | 529 end |
477 | 530 |
478 def accesskey(s) | 531 def accesskey(s) |
479 Redmine::AccessKeys.key_for s | 532 Redmine::AccessKeys.key_for s |
498 end | 551 end |
499 return '' if text.blank? | 552 return '' if text.blank? |
500 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) | 553 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) |
501 only_path = options.delete(:only_path) == false ? false : true | 554 only_path = options.delete(:only_path) == false ? false : true |
502 | 555 |
556 text = text.dup | |
557 macros = catch_macros(text) | |
503 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) | 558 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) |
504 | 559 |
505 @parsed_headings = [] | 560 @parsed_headings = [] |
506 @heading_anchors = {} | 561 @heading_anchors = {} |
507 @current_section = 0 if options[:edit_section_links] | 562 @current_section = 0 if options[:edit_section_links] |
508 | 563 |
509 parse_sections(text, project, obj, attr, only_path, options) | 564 parse_sections(text, project, obj, attr, only_path, options) |
510 text = parse_non_pre_blocks(text) do |text| | 565 text = parse_non_pre_blocks(text, obj, macros) do |text| |
511 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name| | 566 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| |
512 send method_name, text, project, obj, attr, only_path, options | 567 send method_name, text, project, obj, attr, only_path, options |
513 end | 568 end |
514 end | 569 end |
515 parse_headings(text, project, obj, attr, only_path, options) | 570 parse_headings(text, project, obj, attr, only_path, options) |
516 | 571 |
517 if @parsed_headings.any? | 572 if @parsed_headings.any? |
518 replace_toc(text, @parsed_headings) | 573 replace_toc(text, @parsed_headings) |
519 end | 574 end |
520 | 575 |
521 text | 576 text.html_safe |
522 end | 577 end |
523 | 578 |
524 def parse_non_pre_blocks(text) | 579 def parse_non_pre_blocks(text, obj, macros) |
525 s = StringScanner.new(text) | 580 s = StringScanner.new(text) |
526 tags = [] | 581 tags = [] |
527 parsed = '' | 582 parsed = '' |
528 while !s.eos? | 583 while !s.eos? |
529 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) | 584 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) |
530 text, full_tag, closing, tag = s[1], s[2], s[3], s[4] | 585 text, full_tag, closing, tag = s[1], s[2], s[3], s[4] |
531 if tags.empty? | 586 if tags.empty? |
532 yield text | 587 yield text |
588 inject_macros(text, obj, macros) if macros.any? | |
589 else | |
590 inject_macros(text, obj, macros, false) if macros.any? | |
533 end | 591 end |
534 parsed << text | 592 parsed << text |
535 if tag | 593 if tag |
536 if closing | 594 if closing |
537 if tags.last == tag.downcase | 595 if tags.last == tag.downcase |
545 end | 603 end |
546 # Close any non closing tags | 604 # Close any non closing tags |
547 while tag = tags.pop | 605 while tag = tags.pop |
548 parsed << "</#{tag}>" | 606 parsed << "</#{tag}>" |
549 end | 607 end |
550 parsed.html_safe | 608 parsed |
551 end | 609 end |
552 | 610 |
553 def parse_inline_attachments(text, project, obj, attr, only_path, options) | 611 def parse_inline_attachments(text, project, obj, attr, only_path, options) |
554 # when using an image link, try to use an attachment, if possible | 612 # when using an image link, try to use an attachment, if possible |
555 if options[:attachments] || (obj && obj.respond_to?(:attachments)) | 613 attachments = options[:attachments] || [] |
556 attachments = options[:attachments] || obj.attachments | 614 attachments += obj.attachments if obj.respond_to?(:attachments) |
615 if attachments.present? | |
557 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| | 616 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| |
558 filename, ext, alt, alttext = $1.downcase, $2, $3, $4 | 617 filename, ext, alt, alttext = $1.downcase, $2, $3, $4 |
559 # search for the picture in attachments | 618 # search for the picture in attachments |
560 if found = Attachment.latest_attach(attachments, filename) | 619 if found = Attachment.latest_attach(attachments, filename) |
561 image_url = url_for :only_path => only_path, :controller => 'attachments', | 620 image_url = url_for :only_path => only_path, :controller => 'attachments', |
562 :action => 'download', :id => found | 621 :action => 'download', :id => found |
563 desc = found.description.to_s.gsub('"', '') | 622 desc = found.description.to_s.gsub('"', '') |
564 if !desc.blank? && alttext.blank? | 623 if !desc.blank? && alttext.blank? |
565 alt = " title=\"#{desc}\" alt=\"#{desc}\"" | 624 alt = " title=\"#{desc}\" alt=\"#{desc}\"" |
566 end | 625 end |
567 "src=\"#{image_url}\"#{alt}".html_safe | 626 "src=\"#{image_url}\"#{alt}" |
568 else | 627 else |
569 m.html_safe | 628 m |
570 end | 629 end |
571 end | 630 end |
572 end | 631 end |
573 end | 632 end |
574 | 633 |
608 case options[:wiki_links] | 667 case options[:wiki_links] |
609 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') | 668 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') |
610 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export | 669 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export |
611 else | 670 else |
612 wiki_page_id = page.present? ? Wiki.titleize(page) : nil | 671 wiki_page_id = page.present? ? Wiki.titleize(page) : nil |
613 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) | 672 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil |
673 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, | |
674 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) | |
614 end | 675 end |
615 end | 676 end |
616 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) | 677 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) |
617 else | 678 else |
618 # project or wiki doesn't exist | 679 # project or wiki doesn't exist |
619 all.html_safe | 680 all |
620 end | 681 end |
621 else | 682 else |
622 all.html_safe | 683 all |
623 end | 684 end |
624 end | 685 end |
625 end | 686 end |
626 | 687 |
627 # Redmine links | 688 # Redmine links |
654 # Links can refer other objects from other projects, using project identifier: | 715 # Links can refer other objects from other projects, using project identifier: |
655 # identifier:r52 | 716 # identifier:r52 |
656 # identifier:document:"Some document" | 717 # identifier:document:"Some document" |
657 # identifier:version:1.0.0 | 718 # identifier:version:1.0.0 |
658 # identifier:source:some/file | 719 # identifier:source:some/file |
659 def parse_redmine_links(text, project, obj, attr, only_path, options) | 720 def parse_redmine_links(text, default_project, obj, attr, only_path, options) |
660 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| | 721 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| |
661 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 | 722 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 |
662 link = nil | 723 link = nil |
724 project = default_project | |
663 if project_identifier | 725 if project_identifier |
664 project = Project.visible.find_by_identifier(project_identifier) | 726 project = Project.visible.find_by_identifier(project_identifier) |
665 end | 727 end |
666 if esc.nil? | 728 if esc.nil? |
667 if prefix.nil? && sep == 'r' | 729 if prefix.nil? && sep == 'r' |
668 # project.changesets.visible raises an SQL error because of a double join on repositories | 730 if project |
669 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) | 731 repository = nil |
670 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, | 732 if repo_identifier |
671 :class => 'changeset', | 733 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} |
672 :title => truncate_single_line(changeset.comments, :length => 100)) | 734 else |
735 repository = project.repository | |
736 end | |
737 # project.changesets.visible raises an SQL error because of a double join on repositories | |
738 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) | |
739 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}, | |
740 :class => 'changeset', | |
741 :title => truncate_single_line(changeset.comments, :length => 100)) | |
742 end | |
673 end | 743 end |
674 elsif sep == '#' | 744 elsif sep == '#' |
675 oid = identifier.to_i | 745 oid = identifier.to_i |
676 case prefix | 746 case prefix |
677 when nil | 747 when nil |
678 if issue = Issue.visible.find_by_id(oid, :include => :status) | 748 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) |
679 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid}, | 749 anchor = comment_id ? "note-#{comment_id}" : nil |
750 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, | |
680 :class => issue.css_classes, | 751 :class => issue.css_classes, |
681 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") | 752 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") |
682 end | 753 end |
683 when 'document' | 754 when 'document' |
684 if document = Document.visible.find_by_id(oid) | 755 if document = Document.visible.find_by_id(oid) |
731 when 'news' | 802 when 'news' |
732 if project && news = project.news.visible.find_by_title(name) | 803 if project && news = project.news.visible.find_by_title(name) |
733 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, | 804 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, |
734 :class => 'news' | 805 :class => 'news' |
735 end | 806 end |
736 when 'commit' | 807 when 'commit', 'source', 'export' |
737 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) | 808 if project |
738 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, | 809 repository = nil |
739 :class => 'changeset', | 810 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} |
740 :title => truncate_single_line(h(changeset.comments), :length => 100) | 811 repo_prefix, repo_identifier, name = $1, $2, $3 |
741 end | 812 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} |
742 when 'source', 'export' | 813 else |
743 if project && project.repository && User.current.allowed_to?(:browse_repository, project) | 814 repository = project.repository |
744 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} | 815 end |
745 path, rev, anchor = $1, $3, $5 | 816 if prefix == 'commit' |
746 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, | 817 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"])) |
747 :path => to_path_param(path), | 818 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}, |
748 :rev => rev, | 819 :class => 'changeset', |
749 :anchor => anchor, | 820 :title => truncate_single_line(h(changeset.comments), :length => 100) |
750 :format => (prefix == 'export' ? 'raw' : nil)}, | 821 end |
751 :class => (prefix == 'export' ? 'source download' : 'source') | 822 else |
823 if repository && User.current.allowed_to?(:browse_repository, project) | |
824 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} | |
825 path, rev, anchor = $1, $3, $5 | |
826 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, | |
827 :path => to_path_param(path), | |
828 :rev => rev, | |
829 :anchor => anchor}, | |
830 :class => (prefix == 'export' ? 'source download' : 'source') | |
831 end | |
832 end | |
833 repo_prefix = nil | |
752 end | 834 end |
753 when 'attachment' | 835 when 'attachment' |
754 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) | 836 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) |
755 if attachments && attachment = attachments.detect {|a| a.filename == name } | 837 if attachments && attachment = Attachment.latest_attach(attachments, name) |
756 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, | 838 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, |
757 :class => 'attachment' | 839 :class => 'attachment' |
758 end | 840 end |
759 when 'project' | 841 when 'project' |
760 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) | 842 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) |
761 link = link_to_project(p, {:only_path => only_path}, :class => 'project') | 843 link = link_to_project(p, {:only_path => only_path}, :class => 'project') |
762 end | 844 end |
763 end | 845 end |
764 end | 846 end |
765 end | 847 end |
766 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe | 848 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) |
767 end | 849 end |
768 end | 850 end |
769 | 851 |
770 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE) | 852 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) |
771 | 853 |
772 def parse_sections(text, project, obj, attr, only_path, options) | 854 def parse_sections(text, project, obj, attr, only_path, options) |
773 return unless options[:edit_section_links] | 855 return unless options[:edit_section_links] |
774 text.gsub!(HEADING_RE) do | 856 text.gsub!(HEADING_RE) do |
857 heading = $1 | |
775 @current_section += 1 | 858 @current_section += 1 |
776 if @current_section > 1 | 859 if @current_section > 1 |
777 content_tag('div', | 860 content_tag('div', |
778 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), | 861 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), |
779 :class => 'contextual', | 862 :class => 'contextual', |
780 :title => l(:button_edit_section)) + $1 | 863 :title => l(:button_edit_section)) + heading.html_safe |
781 else | 864 else |
782 $1 | 865 heading |
783 end | 866 end |
784 end | 867 end |
785 end | 868 end |
786 | 869 |
787 # Headings and TOC | 870 # Headings and TOC |
803 @parsed_headings << [level, anchor, item] | 886 @parsed_headings << [level, anchor, item] |
804 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" | 887 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" |
805 end | 888 end |
806 end | 889 end |
807 | 890 |
808 MACROS_RE = / | 891 MACROS_RE = /( |
809 (!)? # escaping | 892 (!)? # escaping |
810 ( | 893 ( |
811 \{\{ # opening tag | 894 \{\{ # opening tag |
812 ([\w]+) # macro name | 895 ([\w]+) # macro name |
813 (\(([^\}]*)\))? # optional arguments | 896 (\(([^\n\r]*?)\))? # optional arguments |
897 ([\n\r].*?[\n\r])? # optional block of text | |
814 \}\} # closing tag | 898 \}\} # closing tag |
815 ) | 899 ) |
816 /x unless const_defined?(:MACROS_RE) | 900 )/mx unless const_defined?(:MACROS_RE) |
817 | 901 |
818 # Macros substitution | 902 MACRO_SUB_RE = /( |
819 def parse_macros(text, project, obj, attr, only_path, options) | 903 \{\{ |
904 macro\((\d+)\) | |
905 \}\} | |
906 )/x unless const_defined?(:MACRO_SUB_RE) | |
907 | |
908 # Extracts macros from text | |
909 def catch_macros(text) | |
910 macros = {} | |
820 text.gsub!(MACROS_RE) do | 911 text.gsub!(MACROS_RE) do |
821 esc, all, macro = $1, $2, $3.downcase | 912 all, macro = $1, $4.downcase |
822 args = ($5 || '').split(',').each(&:strip) | 913 if macro_exists?(macro) || all =~ MACRO_SUB_RE |
823 if esc.nil? | 914 index = macros.size |
824 begin | 915 macros[index] = all |
825 exec_macro(macro, obj, args) | 916 "{{macro(#{index})}}" |
826 rescue => e | |
827 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>" | |
828 end || all | |
829 else | 917 else |
830 all | 918 all |
919 end | |
920 end | |
921 macros | |
922 end | |
923 | |
924 # Executes and replaces macros in text | |
925 def inject_macros(text, obj, macros, execute=true) | |
926 text.gsub!(MACRO_SUB_RE) do | |
927 all, index = $1, $2.to_i | |
928 orig = macros.delete(index) | |
929 if execute && orig && orig =~ MACROS_RE | |
930 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) | |
931 if esc.nil? | |
932 h(exec_macro(macro, obj, args, block) || all) | |
933 else | |
934 h(all) | |
935 end | |
936 elsif orig | |
937 h(orig) | |
938 else | |
939 h(all) | |
831 end | 940 end |
832 end | 941 end |
833 end | 942 end |
834 | 943 |
835 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) | 944 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) |
836 | 945 |
837 # Renders the TOC with given headings | 946 # Renders the TOC with given headings |
838 def replace_toc(text, headings) | 947 def replace_toc(text, headings) |
839 text.gsub!(TOC_RE) do | 948 text.gsub!(TOC_RE) do |
949 # Keep only the 4 first levels | |
950 headings = headings.select{|level, anchor, item| level <= 4} | |
840 if headings.empty? | 951 if headings.empty? |
841 '' | 952 '' |
842 else | 953 else |
843 div_class = 'toc' | 954 div_class = 'toc' |
844 div_class << ' right' if $1 == '>' | 955 div_class << ' right' if $1 == '>' |
873 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br | 984 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br |
874 html_safe | 985 html_safe |
875 end | 986 end |
876 | 987 |
877 def lang_options_for_select(blank=true) | 988 def lang_options_for_select(blank=true) |
878 (blank ? [["(auto)", ""]] : []) + | 989 (blank ? [["(auto)", ""]] : []) + languages_options |
879 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last } | |
880 end | 990 end |
881 | 991 |
882 def label_tag_for(name, option_tags = nil, options = {}) | 992 def label_tag_for(name, option_tags = nil, options = {}) |
883 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") | 993 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") |
884 content_tag("label", label_text) | 994 content_tag("label", label_text) |
885 end | 995 end |
886 | 996 |
887 def labelled_tabular_form_for(*args, &proc) | |
888 args << {} unless args.last.is_a?(Hash) | |
889 options = args.last | |
890 options[:html] ||= {} | |
891 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class) | |
892 options.merge!({:builder => TabularFormBuilder}) | |
893 form_for(*args, &proc) | |
894 end | |
895 | |
896 def labelled_form_for(*args, &proc) | 997 def labelled_form_for(*args, &proc) |
897 args << {} unless args.last.is_a?(Hash) | 998 args << {} unless args.last.is_a?(Hash) |
898 options = args.last | 999 options = args.last |
899 options.merge!({:builder => TabularFormBuilder}) | 1000 if args.first.is_a?(Symbol) |
1001 options.merge!(:as => args.shift) | |
1002 end | |
1003 options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) | |
900 form_for(*args, &proc) | 1004 form_for(*args, &proc) |
901 end | 1005 end |
902 | 1006 |
1007 def labelled_fields_for(*args, &proc) | |
1008 args << {} unless args.last.is_a?(Hash) | |
1009 options = args.last | |
1010 options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) | |
1011 fields_for(*args, &proc) | |
1012 end | |
1013 | |
1014 def labelled_remote_form_for(*args, &proc) | |
1015 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." | |
1016 args << {} unless args.last.is_a?(Hash) | |
1017 options = args.last | |
1018 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true}) | |
1019 form_for(*args, &proc) | |
1020 end | |
1021 | |
1022 def error_messages_for(*objects) | |
1023 html = "" | |
1024 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact | |
1025 errors = objects.map {|o| o.errors.full_messages}.flatten | |
1026 if errors.any? | |
1027 html << "<div id='errorExplanation'><ul>\n" | |
1028 errors.each do |error| | |
1029 html << "<li>#{h error}</li>\n" | |
1030 end | |
1031 html << "</ul></div>\n" | |
1032 end | |
1033 html.html_safe | |
1034 end | |
1035 | |
1036 def delete_link(url, options={}) | |
1037 options = { | |
1038 :method => :delete, | |
1039 :data => {:confirm => l(:text_are_you_sure)}, | |
1040 :class => 'icon icon-del' | |
1041 }.merge(options) | |
1042 | |
1043 link_to l(:button_delete), url, options | |
1044 end | |
1045 | |
1046 def preview_link(url, form, target='preview', options={}) | |
1047 content_tag 'a', l(:label_preview), { | |
1048 :href => "#", | |
1049 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, | |
1050 :accesskey => accesskey(:preview) | |
1051 }.merge(options) | |
1052 end | |
1053 | |
1054 def link_to_function(name, function, html_options={}) | |
1055 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) | |
1056 end | |
1057 | |
1058 # Helper to render JSON in views | |
1059 def raw_json(arg) | |
1060 arg.to_json.to_s.gsub('/', '\/').html_safe | |
1061 end | |
1062 | |
1063 def back_url | |
1064 url = params[:back_url] | |
1065 if url.nil? && referer = request.env['HTTP_REFERER'] | |
1066 url = CGI.unescape(referer.to_s) | |
1067 end | |
1068 url | |
1069 end | |
1070 | |
903 def back_url_hidden_field_tag | 1071 def back_url_hidden_field_tag |
904 back_url = params[:back_url] || request.env['HTTP_REFERER'] | 1072 url = back_url |
905 back_url = CGI.unescape(back_url.to_s) | 1073 hidden_field_tag('back_url', url, :id => nil) unless url.blank? |
906 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank? | |
907 end | 1074 end |
908 | 1075 |
909 def check_all_links(form_name) | 1076 def check_all_links(form_name) |
910 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + | 1077 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + |
911 " | ".html_safe + | 1078 " | ".html_safe + |
945 stylesheet_link_tag('context_menu_rtl') | 1112 stylesheet_link_tag('context_menu_rtl') |
946 end | 1113 end |
947 end | 1114 end |
948 @context_menu_included = true | 1115 @context_menu_included = true |
949 end | 1116 end |
950 javascript_tag "new ContextMenu('#{ url_for(url) }')" | 1117 javascript_tag "contextMenuInit('#{ url_for(url) }')" |
951 end | |
952 | |
953 def context_menu_link(name, url, options={}) | |
954 options[:class] ||= '' | |
955 if options.delete(:selected) | |
956 options[:class] << ' icon-checked disabled' | |
957 options[:disabled] = true | |
958 end | |
959 if options.delete(:disabled) | |
960 options.delete(:method) | |
961 options.delete(:confirm) | |
962 options.delete(:onclick) | |
963 options[:class] << ' disabled' | |
964 url = '#' | |
965 end | |
966 link_to h(name), url, options | |
967 end | 1118 end |
968 | 1119 |
969 def calendar_for(field_id) | 1120 def calendar_for(field_id) |
970 include_calendar_headers_tags | 1121 include_calendar_headers_tags |
971 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + | 1122 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") |
972 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });") | |
973 end | 1123 end |
974 | 1124 |
975 def include_calendar_headers_tags | 1125 def include_calendar_headers_tags |
976 unless @calendar_headers_tags_included | 1126 unless @calendar_headers_tags_included |
977 @calendar_headers_tags_included = true | 1127 @calendar_headers_tags_included = true |
978 content_for :header_tags do | 1128 content_for :header_tags do |
979 start_of_week = case Setting.start_of_week.to_i | 1129 start_of_week = Setting.start_of_week |
980 when 1 | 1130 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? |
981 'Calendar._FD = 1;' # Monday | 1131 # Redmine uses 1..7 (monday..sunday) in settings and locales |
982 when 7 | 1132 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 |
983 'Calendar._FD = 0;' # Sunday | 1133 start_of_week = start_of_week.to_i % 7 |
984 when 6 | 1134 |
985 'Calendar._FD = 6;' # Saturday | 1135 tags = javascript_tag( |
1136 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + | |
1137 "showOn: 'button', buttonImageOnly: true, buttonImage: '" + | |
1138 path_to_image('/images/calendar.png') + | |
1139 "', showButtonPanel: true};") | |
1140 jquery_locale = l('jquery.locale', :default => current_language.to_s) | |
1141 unless jquery_locale == 'en' | |
1142 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") | |
1143 end | |
1144 tags | |
1145 end | |
1146 end | |
1147 end | |
1148 | |
1149 # Overrides Rails' stylesheet_link_tag with themes and plugins support. | |
1150 # Examples: | |
1151 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults | |
1152 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets | |
1153 # | |
1154 def stylesheet_link_tag(*sources) | |
1155 options = sources.last.is_a?(Hash) ? sources.pop : {} | |
1156 plugin = options.delete(:plugin) | |
1157 sources = sources.map do |source| | |
1158 if plugin | |
1159 "/plugin_assets/#{plugin}/stylesheets/#{source}" | |
1160 elsif current_theme && current_theme.stylesheets.include?(source) | |
1161 current_theme.stylesheet_path(source) | |
1162 else | |
1163 source | |
1164 end | |
1165 end | |
1166 super sources, options | |
1167 end | |
1168 | |
1169 # Overrides Rails' image_tag with themes and plugins support. | |
1170 # Examples: | |
1171 # image_tag('image.png') # => picks image.png from the current theme or defaults | |
1172 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets | |
1173 # | |
1174 def image_tag(source, options={}) | |
1175 if plugin = options.delete(:plugin) | |
1176 source = "/plugin_assets/#{plugin}/images/#{source}" | |
1177 elsif current_theme && current_theme.images.include?(source) | |
1178 source = current_theme.image_path(source) | |
1179 end | |
1180 super source, options | |
1181 end | |
1182 | |
1183 # Overrides Rails' javascript_include_tag with plugins support | |
1184 # Examples: | |
1185 # javascript_include_tag('scripts') # => picks scripts.js from defaults | |
1186 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets | |
1187 # | |
1188 def javascript_include_tag(*sources) | |
1189 options = sources.last.is_a?(Hash) ? sources.pop : {} | |
1190 if plugin = options.delete(:plugin) | |
1191 sources = sources.map do |source| | |
1192 if plugin | |
1193 "/plugin_assets/#{plugin}/javascripts/#{source}" | |
986 else | 1194 else |
987 '' # use language | 1195 source |
988 end | 1196 end |
989 | 1197 end |
990 javascript_include_tag('calendar/calendar') + | 1198 end |
991 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") + | 1199 super sources, options |
992 javascript_tag(start_of_week) + | |
993 javascript_include_tag('calendar/calendar-setup') + | |
994 stylesheet_link_tag('calendar') | |
995 end | |
996 end | |
997 end | 1200 end |
998 | 1201 |
999 def content_for(name, content = nil, &block) | 1202 def content_for(name, content = nil, &block) |
1000 @has_content ||= {} | 1203 @has_content ||= {} |
1001 @has_content[name] = true | 1204 @has_content[name] = true |
1004 | 1207 |
1005 def has_content?(name) | 1208 def has_content?(name) |
1006 (@has_content && @has_content[name]) || false | 1209 (@has_content && @has_content[name]) || false |
1007 end | 1210 end |
1008 | 1211 |
1212 def sidebar_content? | |
1213 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present? | |
1214 end | |
1215 | |
1216 def view_layouts_base_sidebar_hook_response | |
1217 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) | |
1218 end | |
1219 | |
1009 def email_delivery_enabled? | 1220 def email_delivery_enabled? |
1010 !!ActionMailer::Base.perform_deliveries | 1221 !!ActionMailer::Base.perform_deliveries |
1011 end | 1222 end |
1012 | 1223 |
1013 # Returns the avatar image tag for the given +user+ if avatars are enabled | 1224 # Returns the avatar image tag for the given +user+ if avatars are enabled |
1014 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') | 1225 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') |
1015 def avatar(user, options = { }) | 1226 def avatar(user, options = { }) |
1016 if Setting.gravatar_enabled? | 1227 if Setting.gravatar_enabled? |
1017 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default}) | 1228 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) |
1018 email = nil | 1229 email = nil |
1019 if user.respond_to?(:mail) | 1230 if user.respond_to?(:mail) |
1020 email = user.mail | 1231 email = user.mail |
1021 elsif user.to_s =~ %r{<(.+?)>} | 1232 elsif user.to_s =~ %r{<(.+?)>} |
1022 email = $1 | 1233 email = $1 |
1026 '' | 1237 '' |
1027 end | 1238 end |
1028 end | 1239 end |
1029 | 1240 |
1030 def sanitize_anchor_name(anchor) | 1241 def sanitize_anchor_name(anchor) |
1031 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | 1242 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' |
1243 anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | |
1244 else | |
1245 # TODO: remove when ruby1.8 is no longer supported | |
1246 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | |
1247 end | |
1032 end | 1248 end |
1033 | 1249 |
1034 # Returns the javascript tags that are included in the html layout head | 1250 # Returns the javascript tags that are included in the html layout head |
1035 def javascript_heads | 1251 def javascript_heads |
1036 tags = javascript_include_tag(:defaults) | 1252 tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application') |
1037 unless User.current.pref.warn_on_leaving_unsaved == '0' | 1253 unless User.current.pref.warn_on_leaving_unsaved == '0' |
1038 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });") | 1254 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") |
1039 end | 1255 end |
1040 tags | 1256 tags |
1041 end | 1257 end |
1042 | 1258 |
1043 def favicon | 1259 def favicon |