##// END OF EJS Templates
Fixes section edit links when text includes pre/code tag (#2222)....
Jean-Philippe Lang -
r7715:b3b2eb3e50c5
parent child
Show More
@@ -1,1031 +1,1031
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-2011 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.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 #
37 #
38 # @param [String] name Anchor text (passed to link_to)
38 # @param [String] name Anchor text (passed to link_to)
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] html_options Options passed to link_to
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
46 # Display a link to remote if user is authorized
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 url = options[:url] || {}
48 url = options[:url] || {}
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 end
50 end
51
51
52 # Displays a link to user's account page if active
52 # Displays a link to user's account page if active
53 def link_to_user(user, options={})
53 def link_to_user(user, options={})
54 if user.is_a?(User)
54 if user.is_a?(User)
55 name = h(user.name(options[:format]))
55 name = h(user.name(options[:format]))
56 if user.active?
56 if user.active?
57 link_to name, :controller => 'users', :action => 'show', :id => user
57 link_to name, :controller => 'users', :action => 'show', :id => user
58 else
58 else
59 name
59 name
60 end
60 end
61 else
61 else
62 h(user.to_s)
62 h(user.to_s)
63 end
63 end
64 end
64 end
65
65
66 # Displays a link to +issue+ with its subject.
66 # Displays a link to +issue+ with its subject.
67 # Examples:
67 # Examples:
68 #
68 #
69 # link_to_issue(issue) # => Defect #6: This is the subject
69 # link_to_issue(issue) # => Defect #6: This is the subject
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 # link_to_issue(issue, :subject => false) # => Defect #6
71 # link_to_issue(issue, :subject => false) # => Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 #
73 #
74 def link_to_issue(issue, options={})
74 def link_to_issue(issue, options={})
75 title = nil
75 title = nil
76 subject = nil
76 subject = nil
77 if options[:subject] == false
77 if options[:subject] == false
78 title = truncate(issue.subject, :length => 60)
78 title = truncate(issue.subject, :length => 60)
79 else
79 else
80 subject = issue.subject
80 subject = issue.subject
81 if options[:truncate]
81 if options[:truncate]
82 subject = truncate(subject, :length => options[:truncate])
82 subject = truncate(subject, :length => options[:truncate])
83 end
83 end
84 end
84 end
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 :class => issue.css_classes,
86 :class => issue.css_classes,
87 :title => title
87 :title => title
88 s << ": #{h subject}" if subject
88 s << ": #{h subject}" if subject
89 s = "#{h issue.project} - " + s if options[:project]
89 s = "#{h issue.project} - " + s if options[:project]
90 s
90 s
91 end
91 end
92
92
93 # Generates a link to an attachment.
93 # Generates a link to an attachment.
94 # Options:
94 # Options:
95 # * :text - Link text (default to attachment filename)
95 # * :text - Link text (default to attachment filename)
96 # * :download - Force download (default: false)
96 # * :download - Force download (default: false)
97 def link_to_attachment(attachment, options={})
97 def link_to_attachment(attachment, options={})
98 text = options.delete(:text) || attachment.filename
98 text = options.delete(:text) || attachment.filename
99 action = options.delete(:download) ? 'download' : 'show'
99 action = options.delete(:download) ? 'download' : 'show'
100
100
101 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
101 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
102 end
102 end
103
103
104 # Generates a link to a SCM revision
104 # Generates a link to a SCM revision
105 # Options:
105 # Options:
106 # * :text - Link text (default to the formatted revision)
106 # * :text - Link text (default to the formatted revision)
107 def link_to_revision(revision, project, options={})
107 def link_to_revision(revision, project, options={})
108 text = options.delete(:text) || format_revision(revision)
108 text = options.delete(:text) || format_revision(revision)
109 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
109 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
110
110
111 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
111 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
112 :title => l(:label_revision_id, format_revision(revision)))
112 :title => l(:label_revision_id, format_revision(revision)))
113 end
113 end
114
114
115 # Generates a link to a message
115 # Generates a link to a message
116 def link_to_message(message, options={}, html_options = nil)
116 def link_to_message(message, options={}, html_options = nil)
117 link_to(
117 link_to(
118 h(truncate(message.subject, :length => 60)),
118 h(truncate(message.subject, :length => 60)),
119 { :controller => 'messages', :action => 'show',
119 { :controller => 'messages', :action => 'show',
120 :board_id => message.board_id,
120 :board_id => message.board_id,
121 :id => message.root,
121 :id => message.root,
122 :r => (message.parent_id && message.id),
122 :r => (message.parent_id && message.id),
123 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
123 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
124 }.merge(options),
124 }.merge(options),
125 html_options
125 html_options
126 )
126 )
127 end
127 end
128
128
129 # Generates a link to a project if active
129 # Generates a link to a project if active
130 # Examples:
130 # Examples:
131 #
131 #
132 # link_to_project(project) # => link to the specified project overview
132 # link_to_project(project) # => link to the specified project overview
133 # link_to_project(project, :action=>'settings') # => link to project settings
133 # link_to_project(project, :action=>'settings') # => link to project settings
134 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
134 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
135 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
135 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
136 #
136 #
137 def link_to_project(project, options={}, html_options = nil)
137 def link_to_project(project, options={}, html_options = nil)
138 if project.active?
138 if project.active?
139 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
139 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
140 link_to(h(project), url, html_options)
140 link_to(h(project), url, html_options)
141 else
141 else
142 h(project)
142 h(project)
143 end
143 end
144 end
144 end
145
145
146 def toggle_link(name, id, options={})
146 def toggle_link(name, id, options={})
147 onclick = "Element.toggle('#{id}'); "
147 onclick = "Element.toggle('#{id}'); "
148 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
148 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
149 onclick << "return false;"
149 onclick << "return false;"
150 link_to(name, "#", :onclick => onclick)
150 link_to(name, "#", :onclick => onclick)
151 end
151 end
152
152
153 def image_to_function(name, function, html_options = {})
153 def image_to_function(name, function, html_options = {})
154 html_options.symbolize_keys!
154 html_options.symbolize_keys!
155 tag(:input, html_options.merge({
155 tag(:input, html_options.merge({
156 :type => "image", :src => image_path(name),
156 :type => "image", :src => image_path(name),
157 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
157 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
158 }))
158 }))
159 end
159 end
160
160
161 def prompt_to_remote(name, text, param, url, html_options = {})
161 def prompt_to_remote(name, text, param, url, html_options = {})
162 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
162 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
163 link_to name, {}, html_options
163 link_to name, {}, html_options
164 end
164 end
165
165
166 def format_activity_title(text)
166 def format_activity_title(text)
167 h(truncate_single_line(text, :length => 100))
167 h(truncate_single_line(text, :length => 100))
168 end
168 end
169
169
170 def format_activity_day(date)
170 def format_activity_day(date)
171 date == Date.today ? l(:label_today).titleize : format_date(date)
171 date == Date.today ? l(:label_today).titleize : format_date(date)
172 end
172 end
173
173
174 def format_activity_description(text)
174 def format_activity_description(text)
175 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
175 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
176 end
176 end
177
177
178 def format_version_name(version)
178 def format_version_name(version)
179 if version.project == @project
179 if version.project == @project
180 h(version)
180 h(version)
181 else
181 else
182 h("#{version.project} - #{version}")
182 h("#{version.project} - #{version}")
183 end
183 end
184 end
184 end
185
185
186 def due_date_distance_in_words(date)
186 def due_date_distance_in_words(date)
187 if date
187 if date
188 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
188 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
189 end
189 end
190 end
190 end
191
191
192 def render_page_hierarchy(pages, node=nil, options={})
192 def render_page_hierarchy(pages, node=nil, options={})
193 content = ''
193 content = ''
194 if pages[node]
194 if pages[node]
195 content << "<ul class=\"pages-hierarchy\">\n"
195 content << "<ul class=\"pages-hierarchy\">\n"
196 pages[node].each do |page|
196 pages[node].each do |page|
197 content << "<li>"
197 content << "<li>"
198 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
198 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
199 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
199 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
200 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
200 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
201 content << "</li>\n"
201 content << "</li>\n"
202 end
202 end
203 content << "</ul>\n"
203 content << "</ul>\n"
204 end
204 end
205 content.html_safe
205 content.html_safe
206 end
206 end
207
207
208 # Renders flash messages
208 # Renders flash messages
209 def render_flash_messages
209 def render_flash_messages
210 s = ''
210 s = ''
211 flash.each do |k,v|
211 flash.each do |k,v|
212 s << content_tag('div', v, :class => "flash #{k}")
212 s << content_tag('div', v, :class => "flash #{k}")
213 end
213 end
214 s.html_safe
214 s.html_safe
215 end
215 end
216
216
217 # Renders tabs and their content
217 # Renders tabs and their content
218 def render_tabs(tabs)
218 def render_tabs(tabs)
219 if tabs.any?
219 if tabs.any?
220 render :partial => 'common/tabs', :locals => {:tabs => tabs}
220 render :partial => 'common/tabs', :locals => {:tabs => tabs}
221 else
221 else
222 content_tag 'p', l(:label_no_data), :class => "nodata"
222 content_tag 'p', l(:label_no_data), :class => "nodata"
223 end
223 end
224 end
224 end
225
225
226 # Renders the project quick-jump box
226 # Renders the project quick-jump box
227 def render_project_jump_box
227 def render_project_jump_box
228 return unless User.current.logged?
228 return unless User.current.logged?
229 projects = User.current.memberships.collect(&:project).compact.uniq
229 projects = User.current.memberships.collect(&:project).compact.uniq
230 if projects.any?
230 if projects.any?
231 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
231 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
232 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
232 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
233 '<option value="" disabled="disabled">---</option>'
233 '<option value="" disabled="disabled">---</option>'
234 s << project_tree_options_for_select(projects, :selected => @project) do |p|
234 s << project_tree_options_for_select(projects, :selected => @project) do |p|
235 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
235 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
236 end
236 end
237 s << '</select>'
237 s << '</select>'
238 s.html_safe
238 s.html_safe
239 end
239 end
240 end
240 end
241
241
242 def project_tree_options_for_select(projects, options = {})
242 def project_tree_options_for_select(projects, options = {})
243 s = ''
243 s = ''
244 project_tree(projects) do |project, level|
244 project_tree(projects) do |project, level|
245 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
245 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
246 tag_options = {:value => project.id}
246 tag_options = {:value => project.id}
247 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
247 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
248 tag_options[:selected] = 'selected'
248 tag_options[:selected] = 'selected'
249 else
249 else
250 tag_options[:selected] = nil
250 tag_options[:selected] = nil
251 end
251 end
252 tag_options.merge!(yield(project)) if block_given?
252 tag_options.merge!(yield(project)) if block_given?
253 s << content_tag('option', name_prefix + h(project), tag_options)
253 s << content_tag('option', name_prefix + h(project), tag_options)
254 end
254 end
255 s.html_safe
255 s.html_safe
256 end
256 end
257
257
258 # Yields the given block for each project with its level in the tree
258 # Yields the given block for each project with its level in the tree
259 #
259 #
260 # Wrapper for Project#project_tree
260 # Wrapper for Project#project_tree
261 def project_tree(projects, &block)
261 def project_tree(projects, &block)
262 Project.project_tree(projects, &block)
262 Project.project_tree(projects, &block)
263 end
263 end
264
264
265 def project_nested_ul(projects, &block)
265 def project_nested_ul(projects, &block)
266 s = ''
266 s = ''
267 if projects.any?
267 if projects.any?
268 ancestors = []
268 ancestors = []
269 projects.sort_by(&:lft).each do |project|
269 projects.sort_by(&:lft).each do |project|
270 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
270 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
271 s << "<ul>\n"
271 s << "<ul>\n"
272 else
272 else
273 ancestors.pop
273 ancestors.pop
274 s << "</li>"
274 s << "</li>"
275 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
275 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
276 ancestors.pop
276 ancestors.pop
277 s << "</ul></li>\n"
277 s << "</ul></li>\n"
278 end
278 end
279 end
279 end
280 s << "<li>"
280 s << "<li>"
281 s << yield(project).to_s
281 s << yield(project).to_s
282 ancestors << project
282 ancestors << project
283 end
283 end
284 s << ("</li></ul>\n" * ancestors.size)
284 s << ("</li></ul>\n" * ancestors.size)
285 end
285 end
286 s.html_safe
286 s.html_safe
287 end
287 end
288
288
289 def principals_check_box_tags(name, principals)
289 def principals_check_box_tags(name, principals)
290 s = ''
290 s = ''
291 principals.sort.each do |principal|
291 principals.sort.each do |principal|
292 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
292 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
293 end
293 end
294 s.html_safe
294 s.html_safe
295 end
295 end
296
296
297 # Returns a string for users/groups option tags
297 # Returns a string for users/groups option tags
298 def principals_options_for_select(collection, selected=nil)
298 def principals_options_for_select(collection, selected=nil)
299 s = ''
299 s = ''
300 groups = ''
300 groups = ''
301 collection.sort.each do |element|
301 collection.sort.each do |element|
302 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
302 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
303 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
303 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
304 end
304 end
305 unless groups.empty?
305 unless groups.empty?
306 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
306 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
307 end
307 end
308 s
308 s
309 end
309 end
310
310
311 # Truncates and returns the string as a single line
311 # Truncates and returns the string as a single line
312 def truncate_single_line(string, *args)
312 def truncate_single_line(string, *args)
313 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
313 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
314 end
314 end
315
315
316 # Truncates at line break after 250 characters or options[:length]
316 # Truncates at line break after 250 characters or options[:length]
317 def truncate_lines(string, options={})
317 def truncate_lines(string, options={})
318 length = options[:length] || 250
318 length = options[:length] || 250
319 if string.to_s =~ /\A(.{#{length}}.*?)$/m
319 if string.to_s =~ /\A(.{#{length}}.*?)$/m
320 "#{$1}..."
320 "#{$1}..."
321 else
321 else
322 string
322 string
323 end
323 end
324 end
324 end
325
325
326 def html_hours(text)
326 def html_hours(text)
327 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
327 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
328 end
328 end
329
329
330 def authoring(created, author, options={})
330 def authoring(created, author, options={})
331 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
331 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
332 end
332 end
333
333
334 def time_tag(time)
334 def time_tag(time)
335 text = distance_of_time_in_words(Time.now, time)
335 text = distance_of_time_in_words(Time.now, time)
336 if @project
336 if @project
337 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
337 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
338 else
338 else
339 content_tag('acronym', text, :title => format_time(time))
339 content_tag('acronym', text, :title => format_time(time))
340 end
340 end
341 end
341 end
342
342
343 def syntax_highlight(name, content)
343 def syntax_highlight(name, content)
344 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
344 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
345 end
345 end
346
346
347 def to_path_param(path)
347 def to_path_param(path)
348 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
348 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
349 end
349 end
350
350
351 def pagination_links_full(paginator, count=nil, options={})
351 def pagination_links_full(paginator, count=nil, options={})
352 page_param = options.delete(:page_param) || :page
352 page_param = options.delete(:page_param) || :page
353 per_page_links = options.delete(:per_page_links)
353 per_page_links = options.delete(:per_page_links)
354 url_param = params.dup
354 url_param = params.dup
355
355
356 html = ''
356 html = ''
357 if paginator.current.previous
357 if paginator.current.previous
358 # \xc2\xab(utf-8) = &#171;
358 # \xc2\xab(utf-8) = &#171;
359 html << link_to_content_update(
359 html << link_to_content_update(
360 "\xc2\xab " + l(:label_previous),
360 "\xc2\xab " + l(:label_previous),
361 url_param.merge(page_param => paginator.current.previous)) + ' '
361 url_param.merge(page_param => paginator.current.previous)) + ' '
362 end
362 end
363
363
364 html << (pagination_links_each(paginator, options) do |n|
364 html << (pagination_links_each(paginator, options) do |n|
365 link_to_content_update(n.to_s, url_param.merge(page_param => n))
365 link_to_content_update(n.to_s, url_param.merge(page_param => n))
366 end || '')
366 end || '')
367
367
368 if paginator.current.next
368 if paginator.current.next
369 # \xc2\xbb(utf-8) = &#187;
369 # \xc2\xbb(utf-8) = &#187;
370 html << ' ' + link_to_content_update(
370 html << ' ' + link_to_content_update(
371 (l(:label_next) + " \xc2\xbb"),
371 (l(:label_next) + " \xc2\xbb"),
372 url_param.merge(page_param => paginator.current.next))
372 url_param.merge(page_param => paginator.current.next))
373 end
373 end
374
374
375 unless count.nil?
375 unless count.nil?
376 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
376 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
377 if per_page_links != false && links = per_page_links(paginator.items_per_page)
377 if per_page_links != false && links = per_page_links(paginator.items_per_page)
378 html << " | #{links}"
378 html << " | #{links}"
379 end
379 end
380 end
380 end
381
381
382 html.html_safe
382 html.html_safe
383 end
383 end
384
384
385 def per_page_links(selected=nil)
385 def per_page_links(selected=nil)
386 links = Setting.per_page_options_array.collect do |n|
386 links = Setting.per_page_options_array.collect do |n|
387 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
387 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
388 end
388 end
389 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
389 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
390 end
390 end
391
391
392 def reorder_links(name, url)
392 def reorder_links(name, url)
393 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
393 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
394 url.merge({"#{name}[move_to]" => 'highest'}),
394 url.merge({"#{name}[move_to]" => 'highest'}),
395 :method => :post, :title => l(:label_sort_highest)) +
395 :method => :post, :title => l(:label_sort_highest)) +
396 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
396 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
397 url.merge({"#{name}[move_to]" => 'higher'}),
397 url.merge({"#{name}[move_to]" => 'higher'}),
398 :method => :post, :title => l(:label_sort_higher)) +
398 :method => :post, :title => l(:label_sort_higher)) +
399 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
399 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
400 url.merge({"#{name}[move_to]" => 'lower'}),
400 url.merge({"#{name}[move_to]" => 'lower'}),
401 :method => :post, :title => l(:label_sort_lower)) +
401 :method => :post, :title => l(:label_sort_lower)) +
402 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
402 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
403 url.merge({"#{name}[move_to]" => 'lowest'}),
403 url.merge({"#{name}[move_to]" => 'lowest'}),
404 :method => :post, :title => l(:label_sort_lowest))
404 :method => :post, :title => l(:label_sort_lowest))
405 end
405 end
406
406
407 def breadcrumb(*args)
407 def breadcrumb(*args)
408 elements = args.flatten
408 elements = args.flatten
409 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
409 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
410 end
410 end
411
411
412 def other_formats_links(&block)
412 def other_formats_links(&block)
413 concat('<p class="other-formats">' + l(:label_export_to))
413 concat('<p class="other-formats">' + l(:label_export_to))
414 yield Redmine::Views::OtherFormatsBuilder.new(self)
414 yield Redmine::Views::OtherFormatsBuilder.new(self)
415 concat('</p>')
415 concat('</p>')
416 end
416 end
417
417
418 def page_header_title
418 def page_header_title
419 if @project.nil? || @project.new_record?
419 if @project.nil? || @project.new_record?
420 h(Setting.app_title)
420 h(Setting.app_title)
421 else
421 else
422 b = []
422 b = []
423 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
423 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
424 if ancestors.any?
424 if ancestors.any?
425 root = ancestors.shift
425 root = ancestors.shift
426 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
426 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
427 if ancestors.size > 2
427 if ancestors.size > 2
428 b << "\xe2\x80\xa6"
428 b << "\xe2\x80\xa6"
429 ancestors = ancestors[-2, 2]
429 ancestors = ancestors[-2, 2]
430 end
430 end
431 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
431 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
432 end
432 end
433 b << h(@project)
433 b << h(@project)
434 b.join(" \xc2\xbb ").html_safe
434 b.join(" \xc2\xbb ").html_safe
435 end
435 end
436 end
436 end
437
437
438 def html_title(*args)
438 def html_title(*args)
439 if args.empty?
439 if args.empty?
440 title = []
440 title = []
441 title << @project.name if @project
441 title << @project.name if @project
442 title += @html_title if @html_title
442 title += @html_title if @html_title
443 title << Setting.app_title
443 title << Setting.app_title
444 title.select {|t| !t.blank? }.join(' - ')
444 title.select {|t| !t.blank? }.join(' - ')
445 else
445 else
446 @html_title ||= []
446 @html_title ||= []
447 @html_title += args
447 @html_title += args
448 end
448 end
449 end
449 end
450
450
451 # Returns the theme, controller name, and action as css classes for the
451 # Returns the theme, controller name, and action as css classes for the
452 # HTML body.
452 # HTML body.
453 def body_css_classes
453 def body_css_classes
454 css = []
454 css = []
455 if theme = Redmine::Themes.theme(Setting.ui_theme)
455 if theme = Redmine::Themes.theme(Setting.ui_theme)
456 css << 'theme-' + theme.name
456 css << 'theme-' + theme.name
457 end
457 end
458
458
459 css << 'controller-' + params[:controller]
459 css << 'controller-' + params[:controller]
460 css << 'action-' + params[:action]
460 css << 'action-' + params[:action]
461 css.join(' ')
461 css.join(' ')
462 end
462 end
463
463
464 def accesskey(s)
464 def accesskey(s)
465 Redmine::AccessKeys.key_for s
465 Redmine::AccessKeys.key_for s
466 end
466 end
467
467
468 # Formats text according to system settings.
468 # Formats text according to system settings.
469 # 2 ways to call this method:
469 # 2 ways to call this method:
470 # * with a String: textilizable(text, options)
470 # * with a String: textilizable(text, options)
471 # * with an object and one of its attribute: textilizable(issue, :description, options)
471 # * with an object and one of its attribute: textilizable(issue, :description, options)
472 def textilizable(*args)
472 def textilizable(*args)
473 options = args.last.is_a?(Hash) ? args.pop : {}
473 options = args.last.is_a?(Hash) ? args.pop : {}
474 case args.size
474 case args.size
475 when 1
475 when 1
476 obj = options[:object]
476 obj = options[:object]
477 text = args.shift
477 text = args.shift
478 when 2
478 when 2
479 obj = args.shift
479 obj = args.shift
480 attr = args.shift
480 attr = args.shift
481 text = obj.send(attr).to_s
481 text = obj.send(attr).to_s
482 else
482 else
483 raise ArgumentError, 'invalid arguments to textilizable'
483 raise ArgumentError, 'invalid arguments to textilizable'
484 end
484 end
485 return '' if text.blank?
485 return '' if text.blank?
486 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
486 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
487 only_path = options.delete(:only_path) == false ? false : true
487 only_path = options.delete(:only_path) == false ? false : true
488
488
489 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
489 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
490
490
491 @parsed_headings = []
491 @parsed_headings = []
492 @current_section = 0 if options[:edit_section_links]
492 text = parse_non_pre_blocks(text) do |text|
493 text = parse_non_pre_blocks(text) do |text|
493 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
494 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
494 send method_name, text, project, obj, attr, only_path, options
495 send method_name, text, project, obj, attr, only_path, options
495 end
496 end
496 end
497 end
497
498
498 if @parsed_headings.any?
499 if @parsed_headings.any?
499 replace_toc(text, @parsed_headings)
500 replace_toc(text, @parsed_headings)
500 end
501 end
501
502
502 text
503 text
503 end
504 end
504
505
505 def parse_non_pre_blocks(text)
506 def parse_non_pre_blocks(text)
506 s = StringScanner.new(text)
507 s = StringScanner.new(text)
507 tags = []
508 tags = []
508 parsed = ''
509 parsed = ''
509 while !s.eos?
510 while !s.eos?
510 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
511 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
511 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
512 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
512 if tags.empty?
513 if tags.empty?
513 yield text
514 yield text
514 end
515 end
515 parsed << text
516 parsed << text
516 if tag
517 if tag
517 if closing
518 if closing
518 if tags.last == tag.downcase
519 if tags.last == tag.downcase
519 tags.pop
520 tags.pop
520 end
521 end
521 else
522 else
522 tags << tag.downcase
523 tags << tag.downcase
523 end
524 end
524 parsed << full_tag
525 parsed << full_tag
525 end
526 end
526 end
527 end
527 # Close any non closing tags
528 # Close any non closing tags
528 while tag = tags.pop
529 while tag = tags.pop
529 parsed << "</#{tag}>"
530 parsed << "</#{tag}>"
530 end
531 end
531 parsed.html_safe
532 parsed.html_safe
532 end
533 end
533
534
534 def parse_inline_attachments(text, project, obj, attr, only_path, options)
535 def parse_inline_attachments(text, project, obj, attr, only_path, options)
535 # when using an image link, try to use an attachment, if possible
536 # when using an image link, try to use an attachment, if possible
536 if options[:attachments] || (obj && obj.respond_to?(:attachments))
537 if options[:attachments] || (obj && obj.respond_to?(:attachments))
537 attachments = nil
538 attachments = nil
538 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
539 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
539 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
540 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
540 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
541 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
541 # search for the picture in attachments
542 # search for the picture in attachments
542 if found = attachments.detect { |att| att.filename.downcase == filename }
543 if found = attachments.detect { |att| att.filename.downcase == filename }
543 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
544 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
544 desc = found.description.to_s.gsub('"', '')
545 desc = found.description.to_s.gsub('"', '')
545 if !desc.blank? && alttext.blank?
546 if !desc.blank? && alttext.blank?
546 alt = " title=\"#{desc}\" alt=\"#{desc}\""
547 alt = " title=\"#{desc}\" alt=\"#{desc}\""
547 end
548 end
548 "src=\"#{image_url}\"#{alt}".html_safe
549 "src=\"#{image_url}\"#{alt}".html_safe
549 else
550 else
550 m.html_safe
551 m.html_safe
551 end
552 end
552 end
553 end
553 end
554 end
554 end
555 end
555
556
556 # Wiki links
557 # Wiki links
557 #
558 #
558 # Examples:
559 # Examples:
559 # [[mypage]]
560 # [[mypage]]
560 # [[mypage|mytext]]
561 # [[mypage|mytext]]
561 # wiki links can refer other project wikis, using project name or identifier:
562 # wiki links can refer other project wikis, using project name or identifier:
562 # [[project:]] -> wiki starting page
563 # [[project:]] -> wiki starting page
563 # [[project:|mytext]]
564 # [[project:|mytext]]
564 # [[project:mypage]]
565 # [[project:mypage]]
565 # [[project:mypage|mytext]]
566 # [[project:mypage|mytext]]
566 def parse_wiki_links(text, project, obj, attr, only_path, options)
567 def parse_wiki_links(text, project, obj, attr, only_path, options)
567 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
568 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
568 link_project = project
569 link_project = project
569 esc, all, page, title = $1, $2, $3, $5
570 esc, all, page, title = $1, $2, $3, $5
570 if esc.nil?
571 if esc.nil?
571 if page =~ /^([^\:]+)\:(.*)$/
572 if page =~ /^([^\:]+)\:(.*)$/
572 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
573 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
573 page = $2
574 page = $2
574 title ||= $1 if page.blank?
575 title ||= $1 if page.blank?
575 end
576 end
576
577
577 if link_project && link_project.wiki
578 if link_project && link_project.wiki
578 # extract anchor
579 # extract anchor
579 anchor = nil
580 anchor = nil
580 if page =~ /^(.+?)\#(.+)$/
581 if page =~ /^(.+?)\#(.+)$/
581 page, anchor = $1, $2
582 page, anchor = $1, $2
582 end
583 end
583 anchor = sanitize_anchor_name(anchor) if anchor.present?
584 anchor = sanitize_anchor_name(anchor) if anchor.present?
584 # check if page exists
585 # check if page exists
585 wiki_page = link_project.wiki.find_page(page)
586 wiki_page = link_project.wiki.find_page(page)
586 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
587 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
587 "##{anchor}"
588 "##{anchor}"
588 else
589 else
589 case options[:wiki_links]
590 case options[:wiki_links]
590 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
591 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
591 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
592 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
592 else
593 else
593 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
594 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
594 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
595 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
595 end
596 end
596 end
597 end
597 link_to(title || h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
598 link_to(title || h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
598 else
599 else
599 # project or wiki doesn't exist
600 # project or wiki doesn't exist
600 all.html_safe
601 all.html_safe
601 end
602 end
602 else
603 else
603 all.html_safe
604 all.html_safe
604 end
605 end
605 end
606 end
606 end
607 end
607
608
608 # Redmine links
609 # Redmine links
609 #
610 #
610 # Examples:
611 # Examples:
611 # Issues:
612 # Issues:
612 # #52 -> Link to issue #52
613 # #52 -> Link to issue #52
613 # Changesets:
614 # Changesets:
614 # r52 -> Link to revision 52
615 # r52 -> Link to revision 52
615 # commit:a85130f -> Link to scmid starting with a85130f
616 # commit:a85130f -> Link to scmid starting with a85130f
616 # Documents:
617 # Documents:
617 # document#17 -> Link to document with id 17
618 # document#17 -> Link to document with id 17
618 # document:Greetings -> Link to the document with title "Greetings"
619 # document:Greetings -> Link to the document with title "Greetings"
619 # document:"Some document" -> Link to the document with title "Some document"
620 # document:"Some document" -> Link to the document with title "Some document"
620 # Versions:
621 # Versions:
621 # version#3 -> Link to version with id 3
622 # version#3 -> Link to version with id 3
622 # version:1.0.0 -> Link to version named "1.0.0"
623 # version:1.0.0 -> Link to version named "1.0.0"
623 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
624 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
624 # Attachments:
625 # Attachments:
625 # attachment:file.zip -> Link to the attachment of the current object named file.zip
626 # attachment:file.zip -> Link to the attachment of the current object named file.zip
626 # Source files:
627 # Source files:
627 # source:some/file -> Link to the file located at /some/file in the project's repository
628 # source:some/file -> Link to the file located at /some/file in the project's repository
628 # source:some/file@52 -> Link to the file's revision 52
629 # source:some/file@52 -> Link to the file's revision 52
629 # source:some/file#L120 -> Link to line 120 of the file
630 # source:some/file#L120 -> Link to line 120 of the file
630 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
631 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
631 # export:some/file -> Force the download of the file
632 # export:some/file -> Force the download of the file
632 # Forum messages:
633 # Forum messages:
633 # message#1218 -> Link to message with id 1218
634 # message#1218 -> Link to message with id 1218
634 #
635 #
635 # Links can refer other objects from other projects, using project identifier:
636 # Links can refer other objects from other projects, using project identifier:
636 # identifier:r52
637 # identifier:r52
637 # identifier:document:"Some document"
638 # identifier:document:"Some document"
638 # identifier:version:1.0.0
639 # identifier:version:1.0.0
639 # identifier:source:some/file
640 # identifier:source:some/file
640 def parse_redmine_links(text, project, obj, attr, only_path, options)
641 def parse_redmine_links(text, project, obj, attr, only_path, options)
641 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
642 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
642 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
643 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
643 link = nil
644 link = nil
644 if project_identifier
645 if project_identifier
645 project = Project.visible.find_by_identifier(project_identifier)
646 project = Project.visible.find_by_identifier(project_identifier)
646 end
647 end
647 if esc.nil?
648 if esc.nil?
648 if prefix.nil? && sep == 'r'
649 if prefix.nil? && sep == 'r'
649 # project.changesets.visible raises an SQL error because of a double join on repositories
650 # project.changesets.visible raises an SQL error because of a double join on repositories
650 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
651 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
651 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
652 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
652 :class => 'changeset',
653 :class => 'changeset',
653 :title => truncate_single_line(changeset.comments, :length => 100))
654 :title => truncate_single_line(changeset.comments, :length => 100))
654 end
655 end
655 elsif sep == '#'
656 elsif sep == '#'
656 oid = identifier.to_i
657 oid = identifier.to_i
657 case prefix
658 case prefix
658 when nil
659 when nil
659 if issue = Issue.visible.find_by_id(oid, :include => :status)
660 if issue = Issue.visible.find_by_id(oid, :include => :status)
660 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
661 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
661 :class => issue.css_classes,
662 :class => issue.css_classes,
662 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
663 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
663 end
664 end
664 when 'document'
665 when 'document'
665 if document = Document.visible.find_by_id(oid)
666 if document = Document.visible.find_by_id(oid)
666 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
667 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
667 :class => 'document'
668 :class => 'document'
668 end
669 end
669 when 'version'
670 when 'version'
670 if version = Version.visible.find_by_id(oid)
671 if version = Version.visible.find_by_id(oid)
671 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
672 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
672 :class => 'version'
673 :class => 'version'
673 end
674 end
674 when 'message'
675 when 'message'
675 if message = Message.visible.find_by_id(oid, :include => :parent)
676 if message = Message.visible.find_by_id(oid, :include => :parent)
676 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
677 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
677 end
678 end
678 when 'project'
679 when 'project'
679 if p = Project.visible.find_by_id(oid)
680 if p = Project.visible.find_by_id(oid)
680 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
681 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
681 end
682 end
682 end
683 end
683 elsif sep == ':'
684 elsif sep == ':'
684 # removes the double quotes if any
685 # removes the double quotes if any
685 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
686 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
686 case prefix
687 case prefix
687 when 'document'
688 when 'document'
688 if project && document = project.documents.visible.find_by_title(name)
689 if project && document = project.documents.visible.find_by_title(name)
689 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
690 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
690 :class => 'document'
691 :class => 'document'
691 end
692 end
692 when 'version'
693 when 'version'
693 if project && version = project.versions.visible.find_by_name(name)
694 if project && version = project.versions.visible.find_by_name(name)
694 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
695 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
695 :class => 'version'
696 :class => 'version'
696 end
697 end
697 when 'commit'
698 when 'commit'
698 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
699 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
699 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
700 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
700 :class => 'changeset',
701 :class => 'changeset',
701 :title => truncate_single_line(h(changeset.comments), :length => 100)
702 :title => truncate_single_line(h(changeset.comments), :length => 100)
702 end
703 end
703 when 'source', 'export'
704 when 'source', 'export'
704 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
705 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
705 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
706 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
706 path, rev, anchor = $1, $3, $5
707 path, rev, anchor = $1, $3, $5
707 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
708 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
708 :path => to_path_param(path),
709 :path => to_path_param(path),
709 :rev => rev,
710 :rev => rev,
710 :anchor => anchor,
711 :anchor => anchor,
711 :format => (prefix == 'export' ? 'raw' : nil)},
712 :format => (prefix == 'export' ? 'raw' : nil)},
712 :class => (prefix == 'export' ? 'source download' : 'source')
713 :class => (prefix == 'export' ? 'source download' : 'source')
713 end
714 end
714 when 'attachment'
715 when 'attachment'
715 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
716 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
716 if attachments && attachment = attachments.detect {|a| a.filename == name }
717 if attachments && attachment = attachments.detect {|a| a.filename == name }
717 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
718 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
718 :class => 'attachment'
719 :class => 'attachment'
719 end
720 end
720 when 'project'
721 when 'project'
721 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
722 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
722 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
723 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
723 end
724 end
724 end
725 end
725 end
726 end
726 end
727 end
727 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
728 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
728 end
729 end
729 end
730 end
730
731
731 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
732 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
732
733
733 def parse_sections(text, project, obj, attr, only_path, options)
734 def parse_sections(text, project, obj, attr, only_path, options)
734 return unless options[:edit_section_links]
735 return unless options[:edit_section_links]
735 section = 0
736 text.gsub!(HEADING_RE) do
736 text.gsub!(HEADING_RE) do
737 section += 1
737 @current_section += 1
738 if section > 1
738 if @current_section > 1
739 content_tag('div',
739 content_tag('div',
740 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => section)),
740 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
741 :class => 'contextual',
741 :class => 'contextual',
742 :title => l(:button_edit_section)) + $1
742 :title => l(:button_edit_section)) + $1
743 else
743 else
744 $1
744 $1
745 end
745 end
746 end
746 end
747 end
747 end
748
748
749 # Headings and TOC
749 # Headings and TOC
750 # Adds ids and links to headings unless options[:headings] is set to false
750 # Adds ids and links to headings unless options[:headings] is set to false
751 def parse_headings(text, project, obj, attr, only_path, options)
751 def parse_headings(text, project, obj, attr, only_path, options)
752 return if options[:headings] == false
752 return if options[:headings] == false
753
753
754 text.gsub!(HEADING_RE) do
754 text.gsub!(HEADING_RE) do
755 level, attrs, content = $2.to_i, $3, $4
755 level, attrs, content = $2.to_i, $3, $4
756 item = strip_tags(content).strip
756 item = strip_tags(content).strip
757 anchor = sanitize_anchor_name(item)
757 anchor = sanitize_anchor_name(item)
758 # used for single-file wiki export
758 # used for single-file wiki export
759 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
759 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
760 @parsed_headings << [level, anchor, item]
760 @parsed_headings << [level, anchor, item]
761 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
761 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
762 end
762 end
763 end
763 end
764
764
765 MACROS_RE = /
765 MACROS_RE = /
766 (!)? # escaping
766 (!)? # escaping
767 (
767 (
768 \{\{ # opening tag
768 \{\{ # opening tag
769 ([\w]+) # macro name
769 ([\w]+) # macro name
770 (\(([^\}]*)\))? # optional arguments
770 (\(([^\}]*)\))? # optional arguments
771 \}\} # closing tag
771 \}\} # closing tag
772 )
772 )
773 /x unless const_defined?(:MACROS_RE)
773 /x unless const_defined?(:MACROS_RE)
774
774
775 # Macros substitution
775 # Macros substitution
776 def parse_macros(text, project, obj, attr, only_path, options)
776 def parse_macros(text, project, obj, attr, only_path, options)
777 text.gsub!(MACROS_RE) do
777 text.gsub!(MACROS_RE) do
778 esc, all, macro = $1, $2, $3.downcase
778 esc, all, macro = $1, $2, $3.downcase
779 args = ($5 || '').split(',').each(&:strip)
779 args = ($5 || '').split(',').each(&:strip)
780 if esc.nil?
780 if esc.nil?
781 begin
781 begin
782 exec_macro(macro, obj, args)
782 exec_macro(macro, obj, args)
783 rescue => e
783 rescue => e
784 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
784 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
785 end || all
785 end || all
786 else
786 else
787 all
787 all
788 end
788 end
789 end
789 end
790 end
790 end
791
791
792 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
792 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
793
793
794 # Renders the TOC with given headings
794 # Renders the TOC with given headings
795 def replace_toc(text, headings)
795 def replace_toc(text, headings)
796 text.gsub!(TOC_RE) do
796 text.gsub!(TOC_RE) do
797 if headings.empty?
797 if headings.empty?
798 ''
798 ''
799 else
799 else
800 div_class = 'toc'
800 div_class = 'toc'
801 div_class << ' right' if $1 == '>'
801 div_class << ' right' if $1 == '>'
802 div_class << ' left' if $1 == '<'
802 div_class << ' left' if $1 == '<'
803 out = "<ul class=\"#{div_class}\"><li>"
803 out = "<ul class=\"#{div_class}\"><li>"
804 root = headings.map(&:first).min
804 root = headings.map(&:first).min
805 current = root
805 current = root
806 started = false
806 started = false
807 headings.each do |level, anchor, item|
807 headings.each do |level, anchor, item|
808 if level > current
808 if level > current
809 out << '<ul><li>' * (level - current)
809 out << '<ul><li>' * (level - current)
810 elsif level < current
810 elsif level < current
811 out << "</li></ul>\n" * (current - level) + "</li><li>"
811 out << "</li></ul>\n" * (current - level) + "</li><li>"
812 elsif started
812 elsif started
813 out << '</li><li>'
813 out << '</li><li>'
814 end
814 end
815 out << "<a href=\"##{anchor}\">#{item}</a>"
815 out << "<a href=\"##{anchor}\">#{item}</a>"
816 current = level
816 current = level
817 started = true
817 started = true
818 end
818 end
819 out << '</li></ul>' * (current - root)
819 out << '</li></ul>' * (current - root)
820 out << '</li></ul>'
820 out << '</li></ul>'
821 end
821 end
822 end
822 end
823 end
823 end
824
824
825 # Same as Rails' simple_format helper without using paragraphs
825 # Same as Rails' simple_format helper without using paragraphs
826 def simple_format_without_paragraph(text)
826 def simple_format_without_paragraph(text)
827 text.to_s.
827 text.to_s.
828 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
828 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
829 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
829 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
830 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
830 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
831 html_safe
831 html_safe
832 end
832 end
833
833
834 def lang_options_for_select(blank=true)
834 def lang_options_for_select(blank=true)
835 (blank ? [["(auto)", ""]] : []) +
835 (blank ? [["(auto)", ""]] : []) +
836 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
836 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
837 end
837 end
838
838
839 def label_tag_for(name, option_tags = nil, options = {})
839 def label_tag_for(name, option_tags = nil, options = {})
840 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
840 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
841 content_tag("label", label_text)
841 content_tag("label", label_text)
842 end
842 end
843
843
844 def labelled_tabular_form_for(name, object, options, &proc)
844 def labelled_tabular_form_for(name, object, options, &proc)
845 options[:html] ||= {}
845 options[:html] ||= {}
846 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
846 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
847 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
847 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
848 end
848 end
849
849
850 def back_url_hidden_field_tag
850 def back_url_hidden_field_tag
851 back_url = params[:back_url] || request.env['HTTP_REFERER']
851 back_url = params[:back_url] || request.env['HTTP_REFERER']
852 back_url = CGI.unescape(back_url.to_s)
852 back_url = CGI.unescape(back_url.to_s)
853 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
853 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
854 end
854 end
855
855
856 def check_all_links(form_name)
856 def check_all_links(form_name)
857 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
857 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
858 " | ".html_safe +
858 " | ".html_safe +
859 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
859 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
860 end
860 end
861
861
862 def progress_bar(pcts, options={})
862 def progress_bar(pcts, options={})
863 pcts = [pcts, pcts] unless pcts.is_a?(Array)
863 pcts = [pcts, pcts] unless pcts.is_a?(Array)
864 pcts = pcts.collect(&:round)
864 pcts = pcts.collect(&:round)
865 pcts[1] = pcts[1] - pcts[0]
865 pcts[1] = pcts[1] - pcts[0]
866 pcts << (100 - pcts[1] - pcts[0])
866 pcts << (100 - pcts[1] - pcts[0])
867 width = options[:width] || '100px;'
867 width = options[:width] || '100px;'
868 legend = options[:legend] || ''
868 legend = options[:legend] || ''
869 content_tag('table',
869 content_tag('table',
870 content_tag('tr',
870 content_tag('tr',
871 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
871 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
872 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
872 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
873 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
873 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
874 ), :class => 'progress', :style => "width: #{width};").html_safe +
874 ), :class => 'progress', :style => "width: #{width};").html_safe +
875 content_tag('p', legend, :class => 'pourcent').html_safe
875 content_tag('p', legend, :class => 'pourcent').html_safe
876 end
876 end
877
877
878 def checked_image(checked=true)
878 def checked_image(checked=true)
879 if checked
879 if checked
880 image_tag 'toggle_check.png'
880 image_tag 'toggle_check.png'
881 end
881 end
882 end
882 end
883
883
884 def context_menu(url)
884 def context_menu(url)
885 unless @context_menu_included
885 unless @context_menu_included
886 content_for :header_tags do
886 content_for :header_tags do
887 javascript_include_tag('context_menu') +
887 javascript_include_tag('context_menu') +
888 stylesheet_link_tag('context_menu')
888 stylesheet_link_tag('context_menu')
889 end
889 end
890 if l(:direction) == 'rtl'
890 if l(:direction) == 'rtl'
891 content_for :header_tags do
891 content_for :header_tags do
892 stylesheet_link_tag('context_menu_rtl')
892 stylesheet_link_tag('context_menu_rtl')
893 end
893 end
894 end
894 end
895 @context_menu_included = true
895 @context_menu_included = true
896 end
896 end
897 javascript_tag "new ContextMenu('#{ url_for(url) }')"
897 javascript_tag "new ContextMenu('#{ url_for(url) }')"
898 end
898 end
899
899
900 def context_menu_link(name, url, options={})
900 def context_menu_link(name, url, options={})
901 options[:class] ||= ''
901 options[:class] ||= ''
902 if options.delete(:selected)
902 if options.delete(:selected)
903 options[:class] << ' icon-checked disabled'
903 options[:class] << ' icon-checked disabled'
904 options[:disabled] = true
904 options[:disabled] = true
905 end
905 end
906 if options.delete(:disabled)
906 if options.delete(:disabled)
907 options.delete(:method)
907 options.delete(:method)
908 options.delete(:confirm)
908 options.delete(:confirm)
909 options.delete(:onclick)
909 options.delete(:onclick)
910 options[:class] << ' disabled'
910 options[:class] << ' disabled'
911 url = '#'
911 url = '#'
912 end
912 end
913 link_to h(name), url, options
913 link_to h(name), url, options
914 end
914 end
915
915
916 def calendar_for(field_id)
916 def calendar_for(field_id)
917 include_calendar_headers_tags
917 include_calendar_headers_tags
918 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
918 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
919 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
919 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
920 end
920 end
921
921
922 def include_calendar_headers_tags
922 def include_calendar_headers_tags
923 unless @calendar_headers_tags_included
923 unless @calendar_headers_tags_included
924 @calendar_headers_tags_included = true
924 @calendar_headers_tags_included = true
925 content_for :header_tags do
925 content_for :header_tags do
926 start_of_week = case Setting.start_of_week.to_i
926 start_of_week = case Setting.start_of_week.to_i
927 when 1
927 when 1
928 'Calendar._FD = 1;' # Monday
928 'Calendar._FD = 1;' # Monday
929 when 7
929 when 7
930 'Calendar._FD = 0;' # Sunday
930 'Calendar._FD = 0;' # Sunday
931 when 6
931 when 6
932 'Calendar._FD = 6;' # Saturday
932 'Calendar._FD = 6;' # Saturday
933 else
933 else
934 '' # use language
934 '' # use language
935 end
935 end
936
936
937 javascript_include_tag('calendar/calendar') +
937 javascript_include_tag('calendar/calendar') +
938 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
938 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
939 javascript_tag(start_of_week) +
939 javascript_tag(start_of_week) +
940 javascript_include_tag('calendar/calendar-setup') +
940 javascript_include_tag('calendar/calendar-setup') +
941 stylesheet_link_tag('calendar')
941 stylesheet_link_tag('calendar')
942 end
942 end
943 end
943 end
944 end
944 end
945
945
946 def content_for(name, content = nil, &block)
946 def content_for(name, content = nil, &block)
947 @has_content ||= {}
947 @has_content ||= {}
948 @has_content[name] = true
948 @has_content[name] = true
949 super(name, content, &block)
949 super(name, content, &block)
950 end
950 end
951
951
952 def has_content?(name)
952 def has_content?(name)
953 (@has_content && @has_content[name]) || false
953 (@has_content && @has_content[name]) || false
954 end
954 end
955
955
956 def email_delivery_enabled?
956 def email_delivery_enabled?
957 !!ActionMailer::Base.perform_deliveries
957 !!ActionMailer::Base.perform_deliveries
958 end
958 end
959
959
960 # Returns the avatar image tag for the given +user+ if avatars are enabled
960 # Returns the avatar image tag for the given +user+ if avatars are enabled
961 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
961 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
962 def avatar(user, options = { })
962 def avatar(user, options = { })
963 if Setting.gravatar_enabled?
963 if Setting.gravatar_enabled?
964 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
964 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
965 email = nil
965 email = nil
966 if user.respond_to?(:mail)
966 if user.respond_to?(:mail)
967 email = user.mail
967 email = user.mail
968 elsif user.to_s =~ %r{<(.+?)>}
968 elsif user.to_s =~ %r{<(.+?)>}
969 email = $1
969 email = $1
970 end
970 end
971 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
971 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
972 else
972 else
973 ''
973 ''
974 end
974 end
975 end
975 end
976
976
977 def sanitize_anchor_name(anchor)
977 def sanitize_anchor_name(anchor)
978 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
978 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
979 end
979 end
980
980
981 # Returns the javascript tags that are included in the html layout head
981 # Returns the javascript tags that are included in the html layout head
982 def javascript_heads
982 def javascript_heads
983 tags = javascript_include_tag(:defaults)
983 tags = javascript_include_tag(:defaults)
984 unless User.current.pref.warn_on_leaving_unsaved == '0'
984 unless User.current.pref.warn_on_leaving_unsaved == '0'
985 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
985 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
986 end
986 end
987 tags
987 tags
988 end
988 end
989
989
990 def favicon
990 def favicon
991 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
991 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
992 end
992 end
993
993
994 def robot_exclusion_tag
994 def robot_exclusion_tag
995 '<meta name="robots" content="noindex,follow,noarchive" />'
995 '<meta name="robots" content="noindex,follow,noarchive" />'
996 end
996 end
997
997
998 # Returns true if arg is expected in the API response
998 # Returns true if arg is expected in the API response
999 def include_in_api_response?(arg)
999 def include_in_api_response?(arg)
1000 unless @included_in_api_response
1000 unless @included_in_api_response
1001 param = params[:include]
1001 param = params[:include]
1002 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1002 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1003 @included_in_api_response.collect!(&:strip)
1003 @included_in_api_response.collect!(&:strip)
1004 end
1004 end
1005 @included_in_api_response.include?(arg.to_s)
1005 @included_in_api_response.include?(arg.to_s)
1006 end
1006 end
1007
1007
1008 # Returns options or nil if nometa param or X-Redmine-Nometa header
1008 # Returns options or nil if nometa param or X-Redmine-Nometa header
1009 # was set in the request
1009 # was set in the request
1010 def api_meta(options)
1010 def api_meta(options)
1011 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1011 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1012 # compatibility mode for activeresource clients that raise
1012 # compatibility mode for activeresource clients that raise
1013 # an error when unserializing an array with attributes
1013 # an error when unserializing an array with attributes
1014 nil
1014 nil
1015 else
1015 else
1016 options
1016 options
1017 end
1017 end
1018 end
1018 end
1019
1019
1020 private
1020 private
1021
1021
1022 def wiki_helper
1022 def wiki_helper
1023 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1023 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1024 extend helper
1024 extend helper
1025 return self
1025 return self
1026 end
1026 end
1027
1027
1028 def link_to_content_update(text, url_params = {}, html_options = {})
1028 def link_to_content_update(text, url_params = {}, html_options = {})
1029 link_to(text, url_params, html_options)
1029 link_to(text, url_params, html_options)
1030 end
1030 end
1031 end
1031 end
@@ -1,127 +1,129
1 ---
1 ---
2 wiki_contents_001:
2 wiki_contents_001:
3 text: |-
3 text: |-
4 h1. CookBook documentation
4 h1. CookBook documentation
5
5
6 {{child_pages}}
6 {{child_pages}}
7
7
8 Some updated [[documentation]] here with gzipped history
8 Some updated [[documentation]] here with gzipped history
9 updated_on: 2007-03-07 00:10:51 +01:00
9 updated_on: 2007-03-07 00:10:51 +01:00
10 page_id: 1
10 page_id: 1
11 id: 1
11 id: 1
12 version: 3
12 version: 3
13 author_id: 1
13 author_id: 1
14 comments: Gzip compression activated
14 comments: Gzip compression activated
15 wiki_contents_002:
15 wiki_contents_002:
16 text: |-
16 text: |-
17 h1. Another page
17 h1. Another page
18
18
19 This is a link to a ticket: #2
19 This is a link to a ticket: #2
20 And this is an included page:
20 And this is an included page:
21 {{include(Page with an inline image)}}
21 {{include(Page with an inline image)}}
22 updated_on: 2007-03-08 00:18:07 +01:00
22 updated_on: 2007-03-08 00:18:07 +01:00
23 page_id: 2
23 page_id: 2
24 id: 2
24 id: 2
25 version: 1
25 version: 1
26 author_id: 1
26 author_id: 1
27 comments:
27 comments:
28 wiki_contents_003:
28 wiki_contents_003:
29 text: |-
29 text: |-
30 h1. Start page
30 h1. Start page
31
31
32 E-commerce web site start page
32 E-commerce web site start page
33 updated_on: 2007-03-08 00:18:07 +01:00
33 updated_on: 2007-03-08 00:18:07 +01:00
34 page_id: 3
34 page_id: 3
35 id: 3
35 id: 3
36 version: 1
36 version: 1
37 author_id: 1
37 author_id: 1
38 comments:
38 comments:
39 wiki_contents_004:
39 wiki_contents_004:
40 text: |-
40 text: |-
41 h1. Page with an inline image
41 h1. Page with an inline image
42
42
43 This is an inline image:
43 This is an inline image:
44
44
45 !logo.gif!
45 !logo.gif!
46 updated_on: 2007-03-08 00:18:07 +01:00
46 updated_on: 2007-03-08 00:18:07 +01:00
47 page_id: 4
47 page_id: 4
48 id: 4
48 id: 4
49 version: 1
49 version: 1
50 author_id: 1
50 author_id: 1
51 comments:
51 comments:
52 wiki_contents_005:
52 wiki_contents_005:
53 text: |-
53 text: |-
54 h1. Child page 1
54 h1. Child page 1
55
55
56 This is a child page
56 This is a child page
57 updated_on: 2007-03-08 00:18:07 +01:00
57 updated_on: 2007-03-08 00:18:07 +01:00
58 page_id: 5
58 page_id: 5
59 id: 5
59 id: 5
60 version: 1
60 version: 1
61 author_id: 1
61 author_id: 1
62 comments:
62 comments:
63 wiki_contents_006:
63 wiki_contents_006:
64 text: |-
64 text: |-
65 h1. Child page 2
65 h1. Child page 2
66
66
67 This is a child page
67 This is a child page
68 updated_on: 2007-03-08 00:18:07 +01:00
68 updated_on: 2007-03-08 00:18:07 +01:00
69 page_id: 6
69 page_id: 6
70 id: 6
70 id: 6
71 version: 1
71 version: 1
72 author_id: 1
72 author_id: 1
73 comments:
73 comments:
74 wiki_contents_007:
74 wiki_contents_007:
75 text: This is a child page
75 text: This is a child page
76 updated_on: 2007-03-08 00:18:07 +01:00
76 updated_on: 2007-03-08 00:18:07 +01:00
77 page_id: 7
77 page_id: 7
78 id: 7
78 id: 7
79 version: 1
79 version: 1
80 author_id: 1
80 author_id: 1
81 comments:
81 comments:
82 wiki_contents_008:
82 wiki_contents_008:
83 text: This is a parent page
83 text: This is a parent page
84 updated_on: 2007-03-08 00:18:07 +01:00
84 updated_on: 2007-03-08 00:18:07 +01:00
85 page_id: 8
85 page_id: 8
86 id: 8
86 id: 8
87 version: 1
87 version: 1
88 author_id: 1
88 author_id: 1
89 comments:
89 comments:
90 wiki_contents_009:
90 wiki_contents_009:
91 text: This is a child page
91 text: This is a child page
92 updated_on: 2007-03-08 00:18:07 +01:00
92 updated_on: 2007-03-08 00:18:07 +01:00
93 page_id: 9
93 page_id: 9
94 id: 9
94 id: 9
95 version: 1
95 version: 1
96 author_id: 1
96 author_id: 1
97 comments:
97 comments:
98 wiki_contents_010:
98 wiki_contents_010:
99 text: Page with cyrillic title
99 text: Page with cyrillic title
100 updated_on: 2007-03-08 00:18:07 +01:00
100 updated_on: 2007-03-08 00:18:07 +01:00
101 page_id: 10
101 page_id: 10
102 id: 10
102 id: 10
103 version: 1
103 version: 1
104 author_id: 1
104 author_id: 1
105 comments:
105 comments:
106 wiki_contents_011:
106 wiki_contents_011:
107 text: |-
107 text: |-
108 h1. Title
108 h1. Title
109
109
110 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
110 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
111
111
112 h2. Heading 1
112 h2. Heading 1
113
113
114 @WHATEVER@
115
114 Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
116 Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
115
117
116 Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.
118 Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.
117
119
118 h2. Heading 2
120 h2. Heading 2
119
121
120 Morbi facilisis accumsan orci non pharetra.
122 Morbi facilisis accumsan orci non pharetra.
121 updated_on: 2007-03-08 00:18:07 +01:00
123 updated_on: 2007-03-08 00:18:07 +01:00
122 page_id: 11
124 page_id: 11
123 id: 11
125 id: 11
124 version: 3
126 version: 3
125 author_id: 1
127 author_id: 1
126 comments:
128 comments:
127
129
@@ -1,668 +1,682
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'wiki_controller'
19 require 'wiki_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class WikiController; def rescue_action(e) raise e end; end
22 class WikiController; def rescue_action(e) raise e end; end
23
23
24 class WikiControllerTest < ActionController::TestCase
24 class WikiControllerTest < ActionController::TestCase
25 fixtures :projects, :users, :roles, :members, :member_roles,
25 fixtures :projects, :users, :roles, :members, :member_roles,
26 :enabled_modules, :wikis, :wiki_pages, :wiki_contents,
26 :enabled_modules, :wikis, :wiki_pages, :wiki_contents,
27 :wiki_content_versions, :attachments
27 :wiki_content_versions, :attachments
28
28
29 def setup
29 def setup
30 @controller = WikiController.new
30 @controller = WikiController.new
31 @request = ActionController::TestRequest.new
31 @request = ActionController::TestRequest.new
32 @response = ActionController::TestResponse.new
32 @response = ActionController::TestResponse.new
33 User.current = nil
33 User.current = nil
34 end
34 end
35
35
36 def test_show_start_page
36 def test_show_start_page
37 get :show, :project_id => 'ecookbook'
37 get :show, :project_id => 'ecookbook'
38 assert_response :success
38 assert_response :success
39 assert_template 'show'
39 assert_template 'show'
40 assert_tag :tag => 'h1', :content => /CookBook documentation/
40 assert_tag :tag => 'h1', :content => /CookBook documentation/
41
41
42 # child_pages macro
42 # child_pages macro
43 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
43 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
44 :child => { :tag => 'li',
44 :child => { :tag => 'li',
45 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
45 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
46 :content => 'Page with an inline image' } }
46 :content => 'Page with an inline image' } }
47 end
47 end
48
48
49 def test_show_page_with_name
49 def test_show_page_with_name
50 get :show, :project_id => 1, :id => 'Another_page'
50 get :show, :project_id => 1, :id => 'Another_page'
51 assert_response :success
51 assert_response :success
52 assert_template 'show'
52 assert_template 'show'
53 assert_tag :tag => 'h1', :content => /Another page/
53 assert_tag :tag => 'h1', :content => /Another page/
54 # Included page with an inline image
54 # Included page with an inline image
55 assert_tag :tag => 'p', :content => /This is an inline image/
55 assert_tag :tag => 'p', :content => /This is an inline image/
56 assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3',
56 assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3',
57 :alt => 'This is a logo' }
57 :alt => 'This is a logo' }
58 end
58 end
59
59
60 def test_show_redirected_page
60 def test_show_redirected_page
61 WikiRedirect.create!(:wiki_id => 1, :title => 'Old_title', :redirects_to => 'Another_page')
61 WikiRedirect.create!(:wiki_id => 1, :title => 'Old_title', :redirects_to => 'Another_page')
62
62
63 get :show, :project_id => 'ecookbook', :id => 'Old_title'
63 get :show, :project_id => 'ecookbook', :id => 'Old_title'
64 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
64 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
65 end
65 end
66
66
67 def test_show_with_sidebar
67 def test_show_with_sidebar
68 page = Project.find(1).wiki.pages.new(:title => 'Sidebar')
68 page = Project.find(1).wiki.pages.new(:title => 'Sidebar')
69 page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar')
69 page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar')
70 page.save!
70 page.save!
71
71
72 get :show, :project_id => 1, :id => 'Another_page'
72 get :show, :project_id => 1, :id => 'Another_page'
73 assert_response :success
73 assert_response :success
74 assert_tag :tag => 'div', :attributes => {:id => 'sidebar'},
74 assert_tag :tag => 'div', :attributes => {:id => 'sidebar'},
75 :content => /Side bar content for test_show_with_sidebar/
75 :content => /Side bar content for test_show_with_sidebar/
76 end
76 end
77
77
78 def test_show_unexistent_page_without_edit_right
78 def test_show_unexistent_page_without_edit_right
79 get :show, :project_id => 1, :id => 'Unexistent page'
79 get :show, :project_id => 1, :id => 'Unexistent page'
80 assert_response 404
80 assert_response 404
81 end
81 end
82
83 def test_show_should_display_section_edit_links
84 @request.session[:user_id] = 2
85 get :show, :project_id => 1, :id => 'Page with sections'
86 assert_no_tag 'a', :attributes => {
87 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=1'
88 }
89 assert_tag 'a', :attributes => {
90 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
91 }
92 assert_tag 'a', :attributes => {
93 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=3'
94 }
95 end
82
96
83 def test_show_unexistent_page_with_edit_right
97 def test_show_unexistent_page_with_edit_right
84 @request.session[:user_id] = 2
98 @request.session[:user_id] = 2
85 get :show, :project_id => 1, :id => 'Unexistent page'
99 get :show, :project_id => 1, :id => 'Unexistent page'
86 assert_response :success
100 assert_response :success
87 assert_template 'edit'
101 assert_template 'edit'
88 end
102 end
89
103
90 def test_create_page
104 def test_create_page
91 @request.session[:user_id] = 2
105 @request.session[:user_id] = 2
92 put :update, :project_id => 1,
106 put :update, :project_id => 1,
93 :id => 'New page',
107 :id => 'New page',
94 :content => {:comments => 'Created the page',
108 :content => {:comments => 'Created the page',
95 :text => "h1. New page\n\nThis is a new page",
109 :text => "h1. New page\n\nThis is a new page",
96 :version => 0}
110 :version => 0}
97 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
111 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
98 page = Project.find(1).wiki.find_page('New page')
112 page = Project.find(1).wiki.find_page('New page')
99 assert !page.new_record?
113 assert !page.new_record?
100 assert_not_nil page.content
114 assert_not_nil page.content
101 assert_equal 'Created the page', page.content.comments
115 assert_equal 'Created the page', page.content.comments
102 end
116 end
103
117
104 def test_create_page_with_attachments
118 def test_create_page_with_attachments
105 @request.session[:user_id] = 2
119 @request.session[:user_id] = 2
106 assert_difference 'WikiPage.count' do
120 assert_difference 'WikiPage.count' do
107 assert_difference 'Attachment.count' do
121 assert_difference 'Attachment.count' do
108 put :update, :project_id => 1,
122 put :update, :project_id => 1,
109 :id => 'New page',
123 :id => 'New page',
110 :content => {:comments => 'Created the page',
124 :content => {:comments => 'Created the page',
111 :text => "h1. New page\n\nThis is a new page",
125 :text => "h1. New page\n\nThis is a new page",
112 :version => 0},
126 :version => 0},
113 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
127 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
114 end
128 end
115 end
129 end
116 page = Project.find(1).wiki.find_page('New page')
130 page = Project.find(1).wiki.find_page('New page')
117 assert_equal 1, page.attachments.count
131 assert_equal 1, page.attachments.count
118 assert_equal 'testfile.txt', page.attachments.first.filename
132 assert_equal 'testfile.txt', page.attachments.first.filename
119 end
133 end
120
134
121 def test_edit_page
135 def test_edit_page
122 @request.session[:user_id] = 2
136 @request.session[:user_id] = 2
123 get :edit, :project_id => 'ecookbook', :id => 'Another_page'
137 get :edit, :project_id => 'ecookbook', :id => 'Another_page'
124
138
125 assert_response :success
139 assert_response :success
126 assert_template 'edit'
140 assert_template 'edit'
127
141
128 assert_tag 'textarea',
142 assert_tag 'textarea',
129 :attributes => { :name => 'content[text]' },
143 :attributes => { :name => 'content[text]' },
130 :content => WikiPage.find_by_title('Another_page').content.text
144 :content => WikiPage.find_by_title('Another_page').content.text
131 end
145 end
132
146
133 def test_edit_section
147 def test_edit_section
134 @request.session[:user_id] = 2
148 @request.session[:user_id] = 2
135 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 2
149 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 2
136
150
137 assert_response :success
151 assert_response :success
138 assert_template 'edit'
152 assert_template 'edit'
139
153
140 page = WikiPage.find_by_title('Page_with_sections')
154 page = WikiPage.find_by_title('Page_with_sections')
141 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
155 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
142
156
143 assert_tag 'textarea',
157 assert_tag 'textarea',
144 :attributes => { :name => 'content[text]' },
158 :attributes => { :name => 'content[text]' },
145 :content => section
159 :content => section
146 assert_tag 'input',
160 assert_tag 'input',
147 :attributes => { :name => 'section', :type => 'hidden', :value => '2' }
161 :attributes => { :name => 'section', :type => 'hidden', :value => '2' }
148 assert_tag 'input',
162 assert_tag 'input',
149 :attributes => { :name => 'section_hash', :type => 'hidden', :value => hash }
163 :attributes => { :name => 'section_hash', :type => 'hidden', :value => hash }
150 end
164 end
151
165
152 def test_edit_invalid_section_should_respond_with_404
166 def test_edit_invalid_section_should_respond_with_404
153 @request.session[:user_id] = 2
167 @request.session[:user_id] = 2
154 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 10
168 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 10
155
169
156 assert_response 404
170 assert_response 404
157 end
171 end
158
172
159 def test_update_page
173 def test_update_page
160 @request.session[:user_id] = 2
174 @request.session[:user_id] = 2
161 assert_no_difference 'WikiPage.count' do
175 assert_no_difference 'WikiPage.count' do
162 assert_no_difference 'WikiContent.count' do
176 assert_no_difference 'WikiContent.count' do
163 assert_difference 'WikiContent::Version.count' do
177 assert_difference 'WikiContent::Version.count' do
164 put :update, :project_id => 1,
178 put :update, :project_id => 1,
165 :id => 'Another_page',
179 :id => 'Another_page',
166 :content => {
180 :content => {
167 :comments => "my comments",
181 :comments => "my comments",
168 :text => "edited",
182 :text => "edited",
169 :version => 1
183 :version => 1
170 }
184 }
171 end
185 end
172 end
186 end
173 end
187 end
174 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
188 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
175
189
176 page = Wiki.find(1).pages.find_by_title('Another_page')
190 page = Wiki.find(1).pages.find_by_title('Another_page')
177 assert_equal "edited", page.content.text
191 assert_equal "edited", page.content.text
178 assert_equal 2, page.content.version
192 assert_equal 2, page.content.version
179 assert_equal "my comments", page.content.comments
193 assert_equal "my comments", page.content.comments
180 end
194 end
181
195
182 def test_update_page_with_failure
196 def test_update_page_with_failure
183 @request.session[:user_id] = 2
197 @request.session[:user_id] = 2
184 assert_no_difference 'WikiPage.count' do
198 assert_no_difference 'WikiPage.count' do
185 assert_no_difference 'WikiContent.count' do
199 assert_no_difference 'WikiContent.count' do
186 assert_no_difference 'WikiContent::Version.count' do
200 assert_no_difference 'WikiContent::Version.count' do
187 put :update, :project_id => 1,
201 put :update, :project_id => 1,
188 :id => 'Another_page',
202 :id => 'Another_page',
189 :content => {
203 :content => {
190 :comments => 'a' * 300, # failure here, comment is too long
204 :comments => 'a' * 300, # failure here, comment is too long
191 :text => 'edited',
205 :text => 'edited',
192 :version => 1
206 :version => 1
193 }
207 }
194 end
208 end
195 end
209 end
196 end
210 end
197 assert_response :success
211 assert_response :success
198 assert_template 'edit'
212 assert_template 'edit'
199
213
200 assert_error_tag :descendant => {:content => /Comment is too long/}
214 assert_error_tag :descendant => {:content => /Comment is too long/}
201 assert_tag :tag => 'textarea', :attributes => {:id => 'content_text'}, :content => 'edited'
215 assert_tag :tag => 'textarea', :attributes => {:id => 'content_text'}, :content => 'edited'
202 assert_tag :tag => 'input', :attributes => {:id => 'content_version', :value => '1'}
216 assert_tag :tag => 'input', :attributes => {:id => 'content_version', :value => '1'}
203 end
217 end
204
218
205 def test_update_stale_page_should_not_raise_an_error
219 def test_update_stale_page_should_not_raise_an_error
206 @request.session[:user_id] = 2
220 @request.session[:user_id] = 2
207 c = Wiki.find(1).find_page('Another_page').content
221 c = Wiki.find(1).find_page('Another_page').content
208 c.text = 'Previous text'
222 c.text = 'Previous text'
209 c.save!
223 c.save!
210 assert_equal 2, c.version
224 assert_equal 2, c.version
211
225
212 assert_no_difference 'WikiPage.count' do
226 assert_no_difference 'WikiPage.count' do
213 assert_no_difference 'WikiContent.count' do
227 assert_no_difference 'WikiContent.count' do
214 assert_no_difference 'WikiContent::Version.count' do
228 assert_no_difference 'WikiContent::Version.count' do
215 put :update, :project_id => 1,
229 put :update, :project_id => 1,
216 :id => 'Another_page',
230 :id => 'Another_page',
217 :content => {
231 :content => {
218 :comments => 'My comments',
232 :comments => 'My comments',
219 :text => 'Text should not be lost',
233 :text => 'Text should not be lost',
220 :version => 1
234 :version => 1
221 }
235 }
222 end
236 end
223 end
237 end
224 end
238 end
225 assert_response :success
239 assert_response :success
226 assert_template 'edit'
240 assert_template 'edit'
227 assert_tag :div,
241 assert_tag :div,
228 :attributes => { :class => /error/ },
242 :attributes => { :class => /error/ },
229 :content => /Data has been updated by another user/
243 :content => /Data has been updated by another user/
230 assert_tag 'textarea',
244 assert_tag 'textarea',
231 :attributes => { :name => 'content[text]' },
245 :attributes => { :name => 'content[text]' },
232 :content => /Text should not be lost/
246 :content => /Text should not be lost/
233 assert_tag 'input',
247 assert_tag 'input',
234 :attributes => { :name => 'content[comments]', :value => 'My comments' }
248 :attributes => { :name => 'content[comments]', :value => 'My comments' }
235
249
236 c.reload
250 c.reload
237 assert_equal 'Previous text', c.text
251 assert_equal 'Previous text', c.text
238 assert_equal 2, c.version
252 assert_equal 2, c.version
239 end
253 end
240
254
241 def test_update_section
255 def test_update_section
242 @request.session[:user_id] = 2
256 @request.session[:user_id] = 2
243 page = WikiPage.find_by_title('Page_with_sections')
257 page = WikiPage.find_by_title('Page_with_sections')
244 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
258 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
245 text = page.content.text
259 text = page.content.text
246
260
247 assert_no_difference 'WikiPage.count' do
261 assert_no_difference 'WikiPage.count' do
248 assert_no_difference 'WikiContent.count' do
262 assert_no_difference 'WikiContent.count' do
249 assert_difference 'WikiContent::Version.count' do
263 assert_difference 'WikiContent::Version.count' do
250 put :update, :project_id => 1, :id => 'Page_with_sections',
264 put :update, :project_id => 1, :id => 'Page_with_sections',
251 :content => {
265 :content => {
252 :text => "New section content",
266 :text => "New section content",
253 :version => 3
267 :version => 3
254 },
268 },
255 :section => 2,
269 :section => 2,
256 :section_hash => hash
270 :section_hash => hash
257 end
271 end
258 end
272 end
259 end
273 end
260 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
274 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
261 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.reload.content.text
275 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.reload.content.text
262 end
276 end
263
277
264 def test_update_section_should_allow_stale_page_update
278 def test_update_section_should_allow_stale_page_update
265 @request.session[:user_id] = 2
279 @request.session[:user_id] = 2
266 page = WikiPage.find_by_title('Page_with_sections')
280 page = WikiPage.find_by_title('Page_with_sections')
267 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
281 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
268 text = page.content.text
282 text = page.content.text
269
283
270 assert_no_difference 'WikiPage.count' do
284 assert_no_difference 'WikiPage.count' do
271 assert_no_difference 'WikiContent.count' do
285 assert_no_difference 'WikiContent.count' do
272 assert_difference 'WikiContent::Version.count' do
286 assert_difference 'WikiContent::Version.count' do
273 put :update, :project_id => 1, :id => 'Page_with_sections',
287 put :update, :project_id => 1, :id => 'Page_with_sections',
274 :content => {
288 :content => {
275 :text => "New section content",
289 :text => "New section content",
276 :version => 2 # Current version is 3
290 :version => 2 # Current version is 3
277 },
291 },
278 :section => 2,
292 :section => 2,
279 :section_hash => hash
293 :section_hash => hash
280 end
294 end
281 end
295 end
282 end
296 end
283 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
297 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
284 page.reload
298 page.reload
285 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.content.text
299 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.content.text
286 assert_equal 4, page.content.version
300 assert_equal 4, page.content.version
287 end
301 end
288
302
289 def test_update_section_should_not_allow_stale_section_update
303 def test_update_section_should_not_allow_stale_section_update
290 @request.session[:user_id] = 2
304 @request.session[:user_id] = 2
291
305
292 assert_no_difference 'WikiPage.count' do
306 assert_no_difference 'WikiPage.count' do
293 assert_no_difference 'WikiContent.count' do
307 assert_no_difference 'WikiContent.count' do
294 assert_no_difference 'WikiContent::Version.count' do
308 assert_no_difference 'WikiContent::Version.count' do
295 put :update, :project_id => 1, :id => 'Page_with_sections',
309 put :update, :project_id => 1, :id => 'Page_with_sections',
296 :content => {
310 :content => {
297 :comments => 'My comments',
311 :comments => 'My comments',
298 :text => "Text should not be lost",
312 :text => "Text should not be lost",
299 :version => 3
313 :version => 3
300 },
314 },
301 :section => 2,
315 :section => 2,
302 :section_hash => Digest::MD5.hexdigest("wrong hash")
316 :section_hash => Digest::MD5.hexdigest("wrong hash")
303 end
317 end
304 end
318 end
305 end
319 end
306 assert_response :success
320 assert_response :success
307 assert_template 'edit'
321 assert_template 'edit'
308 assert_tag :div,
322 assert_tag :div,
309 :attributes => { :class => /error/ },
323 :attributes => { :class => /error/ },
310 :content => /Data has been updated by another user/
324 :content => /Data has been updated by another user/
311 assert_tag 'textarea',
325 assert_tag 'textarea',
312 :attributes => { :name => 'content[text]' },
326 :attributes => { :name => 'content[text]' },
313 :content => /Text should not be lost/
327 :content => /Text should not be lost/
314 assert_tag 'input',
328 assert_tag 'input',
315 :attributes => { :name => 'content[comments]', :value => 'My comments' }
329 :attributes => { :name => 'content[comments]', :value => 'My comments' }
316 end
330 end
317
331
318 def test_preview
332 def test_preview
319 @request.session[:user_id] = 2
333 @request.session[:user_id] = 2
320 xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation',
334 xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation',
321 :content => { :comments => '',
335 :content => { :comments => '',
322 :text => 'this is a *previewed text*',
336 :text => 'this is a *previewed text*',
323 :version => 3 }
337 :version => 3 }
324 assert_response :success
338 assert_response :success
325 assert_template 'common/_preview'
339 assert_template 'common/_preview'
326 assert_tag :tag => 'strong', :content => /previewed text/
340 assert_tag :tag => 'strong', :content => /previewed text/
327 end
341 end
328
342
329 def test_preview_new_page
343 def test_preview_new_page
330 @request.session[:user_id] = 2
344 @request.session[:user_id] = 2
331 xhr :post, :preview, :project_id => 1, :id => 'New page',
345 xhr :post, :preview, :project_id => 1, :id => 'New page',
332 :content => { :text => 'h1. New page',
346 :content => { :text => 'h1. New page',
333 :comments => '',
347 :comments => '',
334 :version => 0 }
348 :version => 0 }
335 assert_response :success
349 assert_response :success
336 assert_template 'common/_preview'
350 assert_template 'common/_preview'
337 assert_tag :tag => 'h1', :content => /New page/
351 assert_tag :tag => 'h1', :content => /New page/
338 end
352 end
339
353
340 def test_history
354 def test_history
341 get :history, :project_id => 1, :id => 'CookBook_documentation'
355 get :history, :project_id => 1, :id => 'CookBook_documentation'
342 assert_response :success
356 assert_response :success
343 assert_template 'history'
357 assert_template 'history'
344 assert_not_nil assigns(:versions)
358 assert_not_nil assigns(:versions)
345 assert_equal 3, assigns(:versions).size
359 assert_equal 3, assigns(:versions).size
346 assert_select "input[type=submit][name=commit]"
360 assert_select "input[type=submit][name=commit]"
347 end
361 end
348
362
349 def test_history_with_one_version
363 def test_history_with_one_version
350 get :history, :project_id => 1, :id => 'Another_page'
364 get :history, :project_id => 1, :id => 'Another_page'
351 assert_response :success
365 assert_response :success
352 assert_template 'history'
366 assert_template 'history'
353 assert_not_nil assigns(:versions)
367 assert_not_nil assigns(:versions)
354 assert_equal 1, assigns(:versions).size
368 assert_equal 1, assigns(:versions).size
355 assert_select "input[type=submit][name=commit]", false
369 assert_select "input[type=submit][name=commit]", false
356 end
370 end
357
371
358 def test_diff
372 def test_diff
359 get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => 2, :version_from => 1
373 get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => 2, :version_from => 1
360 assert_response :success
374 assert_response :success
361 assert_template 'diff'
375 assert_template 'diff'
362 assert_tag :tag => 'span', :attributes => { :class => 'diff_in'},
376 assert_tag :tag => 'span', :attributes => { :class => 'diff_in'},
363 :content => /updated/
377 :content => /updated/
364 end
378 end
365
379
366 def test_annotate
380 def test_annotate
367 get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => 2
381 get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => 2
368 assert_response :success
382 assert_response :success
369 assert_template 'annotate'
383 assert_template 'annotate'
370
384
371 # Line 1
385 # Line 1
372 assert_tag :tag => 'tr', :child => {
386 assert_tag :tag => 'tr', :child => {
373 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '1', :sibling => {
387 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '1', :sibling => {
374 :tag => 'td', :attributes => {:class => 'author'}, :content => /John Smith/, :sibling => {
388 :tag => 'td', :attributes => {:class => 'author'}, :content => /John Smith/, :sibling => {
375 :tag => 'td', :content => /h1\. CookBook documentation/
389 :tag => 'td', :content => /h1\. CookBook documentation/
376 }
390 }
377 }
391 }
378 }
392 }
379
393
380 # Line 5
394 # Line 5
381 assert_tag :tag => 'tr', :child => {
395 assert_tag :tag => 'tr', :child => {
382 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '5', :sibling => {
396 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '5', :sibling => {
383 :tag => 'td', :attributes => {:class => 'author'}, :content => /redMine Admin/, :sibling => {
397 :tag => 'td', :attributes => {:class => 'author'}, :content => /redMine Admin/, :sibling => {
384 :tag => 'td', :content => /Some updated \[\[documentation\]\] here/
398 :tag => 'td', :content => /Some updated \[\[documentation\]\] here/
385 }
399 }
386 }
400 }
387 }
401 }
388 end
402 end
389
403
390 def test_get_rename
404 def test_get_rename
391 @request.session[:user_id] = 2
405 @request.session[:user_id] = 2
392 get :rename, :project_id => 1, :id => 'Another_page'
406 get :rename, :project_id => 1, :id => 'Another_page'
393 assert_response :success
407 assert_response :success
394 assert_template 'rename'
408 assert_template 'rename'
395 assert_tag 'option',
409 assert_tag 'option',
396 :attributes => {:value => ''},
410 :attributes => {:value => ''},
397 :content => '',
411 :content => '',
398 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
412 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
399 assert_no_tag 'option',
413 assert_no_tag 'option',
400 :attributes => {:selected => 'selected'},
414 :attributes => {:selected => 'selected'},
401 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
415 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
402 end
416 end
403
417
404 def test_get_rename_child_page
418 def test_get_rename_child_page
405 @request.session[:user_id] = 2
419 @request.session[:user_id] = 2
406 get :rename, :project_id => 1, :id => 'Child_1'
420 get :rename, :project_id => 1, :id => 'Child_1'
407 assert_response :success
421 assert_response :success
408 assert_template 'rename'
422 assert_template 'rename'
409 assert_tag 'option',
423 assert_tag 'option',
410 :attributes => {:value => ''},
424 :attributes => {:value => ''},
411 :content => '',
425 :content => '',
412 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
426 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
413 assert_tag 'option',
427 assert_tag 'option',
414 :attributes => {:value => '2', :selected => 'selected'},
428 :attributes => {:value => '2', :selected => 'selected'},
415 :content => /Another page/,
429 :content => /Another page/,
416 :parent => {
430 :parent => {
417 :tag => 'select',
431 :tag => 'select',
418 :attributes => {:name => 'wiki_page[parent_id]'}
432 :attributes => {:name => 'wiki_page[parent_id]'}
419 }
433 }
420 end
434 end
421
435
422 def test_rename_with_redirect
436 def test_rename_with_redirect
423 @request.session[:user_id] = 2
437 @request.session[:user_id] = 2
424 post :rename, :project_id => 1, :id => 'Another_page',
438 post :rename, :project_id => 1, :id => 'Another_page',
425 :wiki_page => { :title => 'Another renamed page',
439 :wiki_page => { :title => 'Another renamed page',
426 :redirect_existing_links => 1 }
440 :redirect_existing_links => 1 }
427 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
441 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
428 wiki = Project.find(1).wiki
442 wiki = Project.find(1).wiki
429 # Check redirects
443 # Check redirects
430 assert_not_nil wiki.find_page('Another page')
444 assert_not_nil wiki.find_page('Another page')
431 assert_nil wiki.find_page('Another page', :with_redirect => false)
445 assert_nil wiki.find_page('Another page', :with_redirect => false)
432 end
446 end
433
447
434 def test_rename_without_redirect
448 def test_rename_without_redirect
435 @request.session[:user_id] = 2
449 @request.session[:user_id] = 2
436 post :rename, :project_id => 1, :id => 'Another_page',
450 post :rename, :project_id => 1, :id => 'Another_page',
437 :wiki_page => { :title => 'Another renamed page',
451 :wiki_page => { :title => 'Another renamed page',
438 :redirect_existing_links => "0" }
452 :redirect_existing_links => "0" }
439 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
453 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
440 wiki = Project.find(1).wiki
454 wiki = Project.find(1).wiki
441 # Check that there's no redirects
455 # Check that there's no redirects
442 assert_nil wiki.find_page('Another page')
456 assert_nil wiki.find_page('Another page')
443 end
457 end
444
458
445 def test_rename_with_parent_assignment
459 def test_rename_with_parent_assignment
446 @request.session[:user_id] = 2
460 @request.session[:user_id] = 2
447 post :rename, :project_id => 1, :id => 'Another_page',
461 post :rename, :project_id => 1, :id => 'Another_page',
448 :wiki_page => { :title => 'Another page', :redirect_existing_links => "0", :parent_id => '4' }
462 :wiki_page => { :title => 'Another page', :redirect_existing_links => "0", :parent_id => '4' }
449 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
463 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
450 assert_equal WikiPage.find(4), WikiPage.find_by_title('Another_page').parent
464 assert_equal WikiPage.find(4), WikiPage.find_by_title('Another_page').parent
451 end
465 end
452
466
453 def test_rename_with_parent_unassignment
467 def test_rename_with_parent_unassignment
454 @request.session[:user_id] = 2
468 @request.session[:user_id] = 2
455 post :rename, :project_id => 1, :id => 'Child_1',
469 post :rename, :project_id => 1, :id => 'Child_1',
456 :wiki_page => { :title => 'Child 1', :redirect_existing_links => "0", :parent_id => '' }
470 :wiki_page => { :title => 'Child 1', :redirect_existing_links => "0", :parent_id => '' }
457 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Child_1'
471 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Child_1'
458 assert_nil WikiPage.find_by_title('Child_1').parent
472 assert_nil WikiPage.find_by_title('Child_1').parent
459 end
473 end
460
474
461 def test_destroy_child
475 def test_destroy_child
462 @request.session[:user_id] = 2
476 @request.session[:user_id] = 2
463 delete :destroy, :project_id => 1, :id => 'Child_1'
477 delete :destroy, :project_id => 1, :id => 'Child_1'
464 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
478 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
465 end
479 end
466
480
467 def test_destroy_parent
481 def test_destroy_parent
468 @request.session[:user_id] = 2
482 @request.session[:user_id] = 2
469 assert_no_difference('WikiPage.count') do
483 assert_no_difference('WikiPage.count') do
470 delete :destroy, :project_id => 1, :id => 'Another_page'
484 delete :destroy, :project_id => 1, :id => 'Another_page'
471 end
485 end
472 assert_response :success
486 assert_response :success
473 assert_template 'destroy'
487 assert_template 'destroy'
474 end
488 end
475
489
476 def test_destroy_parent_with_nullify
490 def test_destroy_parent_with_nullify
477 @request.session[:user_id] = 2
491 @request.session[:user_id] = 2
478 assert_difference('WikiPage.count', -1) do
492 assert_difference('WikiPage.count', -1) do
479 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'nullify'
493 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'nullify'
480 end
494 end
481 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
495 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
482 assert_nil WikiPage.find_by_id(2)
496 assert_nil WikiPage.find_by_id(2)
483 end
497 end
484
498
485 def test_destroy_parent_with_cascade
499 def test_destroy_parent_with_cascade
486 @request.session[:user_id] = 2
500 @request.session[:user_id] = 2
487 assert_difference('WikiPage.count', -3) do
501 assert_difference('WikiPage.count', -3) do
488 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'destroy'
502 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'destroy'
489 end
503 end
490 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
504 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
491 assert_nil WikiPage.find_by_id(2)
505 assert_nil WikiPage.find_by_id(2)
492 assert_nil WikiPage.find_by_id(5)
506 assert_nil WikiPage.find_by_id(5)
493 end
507 end
494
508
495 def test_destroy_parent_with_reassign
509 def test_destroy_parent_with_reassign
496 @request.session[:user_id] = 2
510 @request.session[:user_id] = 2
497 assert_difference('WikiPage.count', -1) do
511 assert_difference('WikiPage.count', -1) do
498 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'reassign', :reassign_to_id => 1
512 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'reassign', :reassign_to_id => 1
499 end
513 end
500 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
514 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
501 assert_nil WikiPage.find_by_id(2)
515 assert_nil WikiPage.find_by_id(2)
502 assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent
516 assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent
503 end
517 end
504
518
505 def test_index
519 def test_index
506 get :index, :project_id => 'ecookbook'
520 get :index, :project_id => 'ecookbook'
507 assert_response :success
521 assert_response :success
508 assert_template 'index'
522 assert_template 'index'
509 pages = assigns(:pages)
523 pages = assigns(:pages)
510 assert_not_nil pages
524 assert_not_nil pages
511 assert_equal Project.find(1).wiki.pages.size, pages.size
525 assert_equal Project.find(1).wiki.pages.size, pages.size
512 assert_equal pages.first.content.updated_on, pages.first.updated_on
526 assert_equal pages.first.content.updated_on, pages.first.updated_on
513
527
514 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
528 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
515 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/CookBook_documentation' },
529 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/CookBook_documentation' },
516 :content => 'CookBook documentation' },
530 :content => 'CookBook documentation' },
517 :child => { :tag => 'ul',
531 :child => { :tag => 'ul',
518 :child => { :tag => 'li',
532 :child => { :tag => 'li',
519 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
533 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
520 :content => 'Page with an inline image' } } } },
534 :content => 'Page with an inline image' } } } },
521 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Another_page' },
535 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Another_page' },
522 :content => 'Another page' } }
536 :content => 'Another page' } }
523 end
537 end
524
538
525 def test_index_should_include_atom_link
539 def test_index_should_include_atom_link
526 get :index, :project_id => 'ecookbook'
540 get :index, :project_id => 'ecookbook'
527 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
541 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
528 end
542 end
529
543
530 context "GET :export" do
544 context "GET :export" do
531 context "with an authorized user to export the wiki" do
545 context "with an authorized user to export the wiki" do
532 setup do
546 setup do
533 @request.session[:user_id] = 2
547 @request.session[:user_id] = 2
534 get :export, :project_id => 'ecookbook'
548 get :export, :project_id => 'ecookbook'
535 end
549 end
536
550
537 should_respond_with :success
551 should_respond_with :success
538 should_assign_to :pages
552 should_assign_to :pages
539 should_respond_with_content_type "text/html"
553 should_respond_with_content_type "text/html"
540 should "export all of the wiki pages to a single html file" do
554 should "export all of the wiki pages to a single html file" do
541 assert_select "a[name=?]", "CookBook_documentation"
555 assert_select "a[name=?]", "CookBook_documentation"
542 assert_select "a[name=?]", "Another_page"
556 assert_select "a[name=?]", "Another_page"
543 assert_select "a[name=?]", "Page_with_an_inline_image"
557 assert_select "a[name=?]", "Page_with_an_inline_image"
544 end
558 end
545
559
546 end
560 end
547
561
548 context "with an unauthorized user" do
562 context "with an unauthorized user" do
549 setup do
563 setup do
550 get :export, :project_id => 'ecookbook'
564 get :export, :project_id => 'ecookbook'
551
565
552 should_respond_with :redirect
566 should_respond_with :redirect
553 should_redirect_to('wiki index') { {:action => 'show', :project_id => @project, :id => nil} }
567 should_redirect_to('wiki index') { {:action => 'show', :project_id => @project, :id => nil} }
554 end
568 end
555 end
569 end
556 end
570 end
557
571
558 context "GET :date_index" do
572 context "GET :date_index" do
559 setup do
573 setup do
560 get :date_index, :project_id => 'ecookbook'
574 get :date_index, :project_id => 'ecookbook'
561 end
575 end
562
576
563 should_respond_with :success
577 should_respond_with :success
564 should_assign_to :pages
578 should_assign_to :pages
565 should_assign_to :pages_by_date
579 should_assign_to :pages_by_date
566 should_render_template 'wiki/date_index'
580 should_render_template 'wiki/date_index'
567
581
568 should "include atom link" do
582 should "include atom link" do
569 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
583 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
570 end
584 end
571 end
585 end
572
586
573 def test_not_found
587 def test_not_found
574 get :show, :project_id => 999
588 get :show, :project_id => 999
575 assert_response 404
589 assert_response 404
576 end
590 end
577
591
578 def test_protect_page
592 def test_protect_page
579 page = WikiPage.find_by_wiki_id_and_title(1, 'Another_page')
593 page = WikiPage.find_by_wiki_id_and_title(1, 'Another_page')
580 assert !page.protected?
594 assert !page.protected?
581 @request.session[:user_id] = 2
595 @request.session[:user_id] = 2
582 post :protect, :project_id => 1, :id => page.title, :protected => '1'
596 post :protect, :project_id => 1, :id => page.title, :protected => '1'
583 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
597 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
584 assert page.reload.protected?
598 assert page.reload.protected?
585 end
599 end
586
600
587 def test_unprotect_page
601 def test_unprotect_page
588 page = WikiPage.find_by_wiki_id_and_title(1, 'CookBook_documentation')
602 page = WikiPage.find_by_wiki_id_and_title(1, 'CookBook_documentation')
589 assert page.protected?
603 assert page.protected?
590 @request.session[:user_id] = 2
604 @request.session[:user_id] = 2
591 post :protect, :project_id => 1, :id => page.title, :protected => '0'
605 post :protect, :project_id => 1, :id => page.title, :protected => '0'
592 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'CookBook_documentation'
606 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'CookBook_documentation'
593 assert !page.reload.protected?
607 assert !page.reload.protected?
594 end
608 end
595
609
596 def test_show_page_with_edit_link
610 def test_show_page_with_edit_link
597 @request.session[:user_id] = 2
611 @request.session[:user_id] = 2
598 get :show, :project_id => 1
612 get :show, :project_id => 1
599 assert_response :success
613 assert_response :success
600 assert_template 'show'
614 assert_template 'show'
601 assert_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
615 assert_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
602 end
616 end
603
617
604 def test_show_page_without_edit_link
618 def test_show_page_without_edit_link
605 @request.session[:user_id] = 4
619 @request.session[:user_id] = 4
606 get :show, :project_id => 1
620 get :show, :project_id => 1
607 assert_response :success
621 assert_response :success
608 assert_template 'show'
622 assert_template 'show'
609 assert_no_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
623 assert_no_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
610 end
624 end
611
625
612 def test_show_pdf
626 def test_show_pdf
613 @request.session[:user_id] = 2
627 @request.session[:user_id] = 2
614 get :show, :project_id => 1, :format => 'pdf'
628 get :show, :project_id => 1, :format => 'pdf'
615 assert_response :success
629 assert_response :success
616 assert_not_nil assigns(:page)
630 assert_not_nil assigns(:page)
617 assert_equal 'application/pdf', @response.content_type
631 assert_equal 'application/pdf', @response.content_type
618 assert_equal 'attachment; filename="CookBook_documentation.pdf"',
632 assert_equal 'attachment; filename="CookBook_documentation.pdf"',
619 @response.headers['Content-Disposition']
633 @response.headers['Content-Disposition']
620 end
634 end
621
635
622 def test_show_html
636 def test_show_html
623 @request.session[:user_id] = 2
637 @request.session[:user_id] = 2
624 get :show, :project_id => 1, :format => 'html'
638 get :show, :project_id => 1, :format => 'html'
625 assert_response :success
639 assert_response :success
626 assert_not_nil assigns(:page)
640 assert_not_nil assigns(:page)
627 assert_equal 'text/html', @response.content_type
641 assert_equal 'text/html', @response.content_type
628 assert_equal 'attachment; filename="CookBook_documentation.html"',
642 assert_equal 'attachment; filename="CookBook_documentation.html"',
629 @response.headers['Content-Disposition']
643 @response.headers['Content-Disposition']
630 end
644 end
631
645
632 def test_show_txt
646 def test_show_txt
633 @request.session[:user_id] = 2
647 @request.session[:user_id] = 2
634 get :show, :project_id => 1, :format => 'txt'
648 get :show, :project_id => 1, :format => 'txt'
635 assert_response :success
649 assert_response :success
636 assert_not_nil assigns(:page)
650 assert_not_nil assigns(:page)
637 assert_equal 'text/plain', @response.content_type
651 assert_equal 'text/plain', @response.content_type
638 assert_equal 'attachment; filename="CookBook_documentation.txt"',
652 assert_equal 'attachment; filename="CookBook_documentation.txt"',
639 @response.headers['Content-Disposition']
653 @response.headers['Content-Disposition']
640 end
654 end
641
655
642 def test_edit_unprotected_page
656 def test_edit_unprotected_page
643 # Non members can edit unprotected wiki pages
657 # Non members can edit unprotected wiki pages
644 @request.session[:user_id] = 4
658 @request.session[:user_id] = 4
645 get :edit, :project_id => 1, :id => 'Another_page'
659 get :edit, :project_id => 1, :id => 'Another_page'
646 assert_response :success
660 assert_response :success
647 assert_template 'edit'
661 assert_template 'edit'
648 end
662 end
649
663
650 def test_edit_protected_page_by_nonmember
664 def test_edit_protected_page_by_nonmember
651 # Non members can't edit protected wiki pages
665 # Non members can't edit protected wiki pages
652 @request.session[:user_id] = 4
666 @request.session[:user_id] = 4
653 get :edit, :project_id => 1, :id => 'CookBook_documentation'
667 get :edit, :project_id => 1, :id => 'CookBook_documentation'
654 assert_response 403
668 assert_response 403
655 end
669 end
656
670
657 def test_edit_protected_page_by_member
671 def test_edit_protected_page_by_member
658 @request.session[:user_id] = 2
672 @request.session[:user_id] = 2
659 get :edit, :project_id => 1, :id => 'CookBook_documentation'
673 get :edit, :project_id => 1, :id => 'CookBook_documentation'
660 assert_response :success
674 assert_response :success
661 assert_template 'edit'
675 assert_template 'edit'
662 end
676 end
663
677
664 def test_history_of_non_existing_page_should_return_404
678 def test_history_of_non_existing_page_should_return_404
665 get :history, :project_id => 1, :id => 'Unknown_page'
679 get :history, :project_id => 1, :id => 'Unknown_page'
666 assert_response 404
680 assert_response 404
667 end
681 end
668 end
682 end
General Comments 0
You need to be logged in to leave comments. Login now