##// END OF EJS Templates
Refactor: convert WikiController to a REST resource...
Eric Davis -
r4189:c514dd6885af
parent child
Show More
@@ -1,277 +1,277
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'diff'
19 19
20 20 # The WikiController follows the Rails REST controller pattern but with
21 21 # a few differences
22 22 #
23 23 # * index - shows a list of WikiPages grouped by page or date
24 24 # * new - not used
25 25 # * create - not used
26 26 # * show - will also show the form for creating a new wiki page
27 27 # * edit - used to edit an existing or new page
28 28 # * update - used to save a wiki page update to the database, including new pages
29 29 # * destroy - normal
30 30 #
31 31 # Other member and collection methods are also used
32 32 #
33 33 # TODO: still being worked on
34 34 class WikiController < ApplicationController
35 35 default_search_scope :wiki_pages
36 36 before_filter :find_wiki, :authorize
37 37 before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
38 38
39 39 verify :method => :post, :only => [:protect], :redirect_to => { :action => :show }
40 40
41 41 helper :attachments
42 42 include AttachmentsHelper
43 43 helper :watchers
44 44
45 45 # List of pages, sorted alphabetically and by parent (hierarchy)
46 46 def index
47 47 load_pages_grouped_by_date_without_content
48 48 end
49 49
50 50 # display a page (in editing mode if it doesn't exist)
51 51 def show
52 52 page_title = params[:id]
53 53 @page = @wiki.find_or_new_page(page_title)
54 54 if @page.new_record?
55 55 if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
56 56 edit
57 57 render :action => 'edit'
58 58 else
59 59 render_404
60 60 end
61 61 return
62 62 end
63 63 if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
64 64 # Redirects user to the current version if he's not allowed to view previous versions
65 65 redirect_to :version => nil
66 66 return
67 67 end
68 68 @content = @page.content_for_version(params[:version])
69 69 if User.current.allowed_to?(:export_wiki_pages, @project)
70 70 if params[:format] == 'html'
71 71 export = render_to_string :action => 'export', :layout => false
72 72 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
73 73 return
74 74 elsif params[:format] == 'txt'
75 75 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
76 76 return
77 77 end
78 78 end
79 79 @editable = editable?
80 80 render :action => 'show'
81 81 end
82 82
83 83 # edit an existing page or a new one
84 84 def edit
85 85 @page = @wiki.find_or_new_page(params[:id])
86 86 return render_403 unless editable?
87 87 @page.content = WikiContent.new(:page => @page) if @page.new_record?
88 88
89 89 @content = @page.content_for_version(params[:version])
90 90 @content.text = initial_page_content(@page) if @content.text.blank?
91 91 # don't keep previous comment
92 92 @content.comments = nil
93 93
94 94 # To prevent StaleObjectError exception when reverting to a previous version
95 95 @content.version = @page.content.version
96 96 rescue ActiveRecord::StaleObjectError
97 97 # Optimistic locking exception
98 98 flash[:error] = l(:notice_locking_conflict)
99 99 end
100 100
101 verify :method => :post, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
101 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
102 102 # Creates a new page or updates an existing one
103 103 def update
104 104 @page = @wiki.find_or_new_page(params[:id])
105 105 return render_403 unless editable?
106 106 @page.content = WikiContent.new(:page => @page) if @page.new_record?
107 107
108 108 @content = @page.content_for_version(params[:version])
109 109 @content.text = initial_page_content(@page) if @content.text.blank?
110 110 # don't keep previous comment
111 111 @content.comments = nil
112 112
113 113 if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text]
114 114 attachments = Attachment.attach_files(@page, params[:attachments])
115 115 render_attachment_warning_if_needed(@page)
116 116 # don't save if text wasn't changed
117 117 redirect_to :action => 'show', :project_id => @project, :id => @page.title
118 118 return
119 119 end
120 120 @content.attributes = params[:content]
121 121 @content.author = User.current
122 122 # if page is new @page.save will also save content, but not if page isn't a new record
123 123 if (@page.new_record? ? @page.save : @content.save)
124 124 attachments = Attachment.attach_files(@page, params[:attachments])
125 125 render_attachment_warning_if_needed(@page)
126 126 call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
127 127 redirect_to :action => 'show', :project_id => @project, :id => @page.title
128 128 end
129 129
130 130 rescue ActiveRecord::StaleObjectError
131 131 # Optimistic locking exception
132 132 flash[:error] = l(:notice_locking_conflict)
133 133 end
134 134
135 135 # rename a page
136 136 def rename
137 137 return render_403 unless editable?
138 138 @page.redirect_existing_links = true
139 139 # used to display the *original* title if some AR validation errors occur
140 140 @original_title = @page.pretty_title
141 141 if request.post? && @page.update_attributes(params[:wiki_page])
142 142 flash[:notice] = l(:notice_successful_update)
143 143 redirect_to :action => 'show', :project_id => @project, :id => @page.title
144 144 end
145 145 end
146 146
147 147 def protect
148 148 @page.update_attribute :protected, params[:protected]
149 149 redirect_to :action => 'show', :project_id => @project, :id => @page.title
150 150 end
151 151
152 152 # show page history
153 153 def history
154 154 @version_count = @page.content.versions.count
155 155 @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
156 156 # don't load text
157 157 @versions = @page.content.versions.find :all,
158 158 :select => "id, author_id, comments, updated_on, version",
159 159 :order => 'version DESC',
160 160 :limit => @version_pages.items_per_page + 1,
161 161 :offset => @version_pages.current.offset
162 162
163 163 render :layout => false if request.xhr?
164 164 end
165 165
166 166 def diff
167 167 @diff = @page.diff(params[:version], params[:version_from])
168 168 render_404 unless @diff
169 169 end
170 170
171 171 def annotate
172 172 @annotate = @page.annotate(params[:version])
173 173 render_404 unless @annotate
174 174 end
175 175
176 176 verify :method => :delete, :only => [:destroy], :redirect_to => { :action => :show }
177 177 # Removes a wiki page and its history
178 178 # Children can be either set as root pages, removed or reassigned to another parent page
179 179 def destroy
180 180 return render_403 unless editable?
181 181
182 182 @descendants_count = @page.descendants.size
183 183 if @descendants_count > 0
184 184 case params[:todo]
185 185 when 'nullify'
186 186 # Nothing to do
187 187 when 'destroy'
188 188 # Removes all its descendants
189 189 @page.descendants.each(&:destroy)
190 190 when 'reassign'
191 191 # Reassign children to another parent page
192 192 reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
193 193 return unless reassign_to
194 194 @page.children.each do |child|
195 195 child.update_attribute(:parent, reassign_to)
196 196 end
197 197 else
198 198 @reassignable_to = @wiki.pages - @page.self_and_descendants
199 199 return
200 200 end
201 201 end
202 202 @page.destroy
203 203 redirect_to :action => 'index', :project_id => @project
204 204 end
205 205
206 206 # Export wiki to a single html file
207 207 def export
208 208 if User.current.allowed_to?(:export_wiki_pages, @project)
209 209 @pages = @wiki.pages.find :all, :order => 'title'
210 210 export = render_to_string :action => 'export_multiple', :layout => false
211 211 send_data(export, :type => 'text/html', :filename => "wiki.html")
212 212 else
213 213 redirect_to :action => 'show', :project_id => @project, :id => nil
214 214 end
215 215 end
216 216
217 217 def date_index
218 218 load_pages_grouped_by_date_without_content
219 219 end
220 220
221 221 def preview
222 222 page = @wiki.find_page(params[:id])
223 223 # page is nil when previewing a new page
224 224 return render_403 unless page.nil? || editable?(page)
225 225 if page
226 226 @attachements = page.attachments
227 227 @previewed = page.content
228 228 end
229 229 @text = params[:content][:text]
230 230 render :partial => 'common/preview'
231 231 end
232 232
233 233 def add_attachment
234 234 return render_403 unless editable?
235 235 attachments = Attachment.attach_files(@page, params[:attachments])
236 236 render_attachment_warning_if_needed(@page)
237 redirect_to :action => 'show', :id => @page.title
237 redirect_to :action => 'show', :id => @page.title, :project_id => @project
238 238 end
239 239
240 240 private
241 241
242 242 def find_wiki
243 243 @project = Project.find(params[:project_id])
244 244 @wiki = @project.wiki
245 245 render_404 unless @wiki
246 246 rescue ActiveRecord::RecordNotFound
247 247 render_404
248 248 end
249 249
250 250 # Finds the requested page and returns a 404 error if it doesn't exist
251 251 def find_existing_page
252 252 @page = @wiki.find_page(params[:id])
253 253 render_404 if @page.nil?
254 254 end
255 255
256 256 # Returns true if the current user is allowed to edit the page, otherwise false
257 257 def editable?(page = @page)
258 258 page.editable_by?(User.current)
259 259 end
260 260
261 261 # Returns the default content of a new wiki page
262 262 def initial_page_content(page)
263 263 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
264 264 extend helper unless self.instance_of?(helper)
265 265 helper.instance_method(:initial_page_content).bind(self).call(page)
266 266 end
267 267
268 268 # eager load information about last updates, without loading text
269 269 def load_pages_grouped_by_date_without_content
270 270 @pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
271 271 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
272 272 :order => 'title'
273 273 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
274 274 @pages_by_parent_id = @pages.group_by(&:parent_id)
275 275 end
276 276
277 277 end
@@ -1,840 +1,841
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'forwardable'
19 19 require 'cgi'
20 20
21 21 module ApplicationHelper
22 22 include Redmine::WikiFormatting::Macros::Definitions
23 23 include Redmine::I18n
24 24 include GravatarHelper::PublicMethods
25 25
26 26 extend Forwardable
27 27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28 28
29 29 # Return true if user is authorized for controller/action, otherwise false
30 30 def authorize_for(controller, action)
31 31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
32 32 end
33 33
34 34 # Display a link if user is authorized
35 35 #
36 36 # @param [String] name Anchor text (passed to link_to)
37 37 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
38 38 # @param [optional, Hash] html_options Options passed to link_to
39 39 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
40 40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
42 42 end
43 43
44 44 # Display a link to remote if user is authorized
45 45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
46 46 url = options[:url] || {}
47 47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
48 48 end
49 49
50 50 # Displays a link to user's account page if active
51 51 def link_to_user(user, options={})
52 52 if user.is_a?(User)
53 53 name = h(user.name(options[:format]))
54 54 if user.active?
55 55 link_to name, :controller => 'users', :action => 'show', :id => user
56 56 else
57 57 name
58 58 end
59 59 else
60 60 h(user.to_s)
61 61 end
62 62 end
63 63
64 64 # Displays a link to +issue+ with its subject.
65 65 # Examples:
66 66 #
67 67 # link_to_issue(issue) # => Defect #6: This is the subject
68 68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
69 69 # link_to_issue(issue, :subject => false) # => Defect #6
70 70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
71 71 #
72 72 def link_to_issue(issue, options={})
73 73 title = nil
74 74 subject = nil
75 75 if options[:subject] == false
76 76 title = truncate(issue.subject, :length => 60)
77 77 else
78 78 subject = issue.subject
79 79 if options[:truncate]
80 80 subject = truncate(subject, :length => options[:truncate])
81 81 end
82 82 end
83 83 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
84 84 :class => issue.css_classes,
85 85 :title => title
86 86 s << ": #{h subject}" if subject
87 87 s = "#{h issue.project} - " + s if options[:project]
88 88 s
89 89 end
90 90
91 91 # Generates a link to an attachment.
92 92 # Options:
93 93 # * :text - Link text (default to attachment filename)
94 94 # * :download - Force download (default: false)
95 95 def link_to_attachment(attachment, options={})
96 96 text = options.delete(:text) || attachment.filename
97 97 action = options.delete(:download) ? 'download' : 'show'
98 98
99 99 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
100 100 end
101 101
102 102 # Generates a link to a SCM revision
103 103 # Options:
104 104 # * :text - Link text (default to the formatted revision)
105 105 def link_to_revision(revision, project, options={})
106 106 text = options.delete(:text) || format_revision(revision)
107 107
108 108 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
109 109 end
110 110
111 111 # Generates a link to a project if active
112 112 # Examples:
113 113 #
114 114 # link_to_project(project) # => link to the specified project overview
115 115 # link_to_project(project, :action=>'settings') # => link to project settings
116 116 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
117 117 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
118 118 #
119 119 def link_to_project(project, options={}, html_options = nil)
120 120 if project.active?
121 121 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
122 122 link_to(h(project), url, html_options)
123 123 else
124 124 h(project)
125 125 end
126 126 end
127 127
128 128 def toggle_link(name, id, options={})
129 129 onclick = "Element.toggle('#{id}'); "
130 130 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
131 131 onclick << "return false;"
132 132 link_to(name, "#", :onclick => onclick)
133 133 end
134 134
135 135 def image_to_function(name, function, html_options = {})
136 136 html_options.symbolize_keys!
137 137 tag(:input, html_options.merge({
138 138 :type => "image", :src => image_path(name),
139 139 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
140 140 }))
141 141 end
142 142
143 143 def prompt_to_remote(name, text, param, url, html_options = {})
144 144 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
145 145 link_to name, {}, html_options
146 146 end
147 147
148 148 def format_activity_title(text)
149 149 h(truncate_single_line(text, :length => 100))
150 150 end
151 151
152 152 def format_activity_day(date)
153 153 date == Date.today ? l(:label_today).titleize : format_date(date)
154 154 end
155 155
156 156 def format_activity_description(text)
157 157 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
158 158 end
159 159
160 160 def format_version_name(version)
161 161 if version.project == @project
162 162 h(version)
163 163 else
164 164 h("#{version.project} - #{version}")
165 165 end
166 166 end
167 167
168 168 def due_date_distance_in_words(date)
169 169 if date
170 170 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
171 171 end
172 172 end
173 173
174 174 def render_page_hierarchy(pages, node=nil)
175 175 content = ''
176 176 if pages[node]
177 177 content << "<ul class=\"pages-hierarchy\">\n"
178 178 pages[node].each do |page|
179 179 content << "<li>"
180 180 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
181 181 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
182 182 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
183 183 content << "</li>\n"
184 184 end
185 185 content << "</ul>\n"
186 186 end
187 187 content
188 188 end
189 189
190 190 # Renders flash messages
191 191 def render_flash_messages
192 192 s = ''
193 193 flash.each do |k,v|
194 194 s << content_tag('div', v, :class => "flash #{k}")
195 195 end
196 196 s
197 197 end
198 198
199 199 # Renders tabs and their content
200 200 def render_tabs(tabs)
201 201 if tabs.any?
202 202 render :partial => 'common/tabs', :locals => {:tabs => tabs}
203 203 else
204 204 content_tag 'p', l(:label_no_data), :class => "nodata"
205 205 end
206 206 end
207 207
208 208 # Renders the project quick-jump box
209 209 def render_project_jump_box
210 210 # Retrieve them now to avoid a COUNT query
211 211 projects = User.current.projects.all
212 212 if projects.any?
213 213 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
214 214 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
215 215 '<option value="" disabled="disabled">---</option>'
216 216 s << project_tree_options_for_select(projects, :selected => @project) do |p|
217 217 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
218 218 end
219 219 s << '</select>'
220 220 s
221 221 end
222 222 end
223 223
224 224 def project_tree_options_for_select(projects, options = {})
225 225 s = ''
226 226 project_tree(projects) do |project, level|
227 227 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
228 228 tag_options = {:value => project.id}
229 229 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
230 230 tag_options[:selected] = 'selected'
231 231 else
232 232 tag_options[:selected] = nil
233 233 end
234 234 tag_options.merge!(yield(project)) if block_given?
235 235 s << content_tag('option', name_prefix + h(project), tag_options)
236 236 end
237 237 s
238 238 end
239 239
240 240 # Yields the given block for each project with its level in the tree
241 241 #
242 242 # Wrapper for Project#project_tree
243 243 def project_tree(projects, &block)
244 244 Project.project_tree(projects, &block)
245 245 end
246 246
247 247 def project_nested_ul(projects, &block)
248 248 s = ''
249 249 if projects.any?
250 250 ancestors = []
251 251 projects.sort_by(&:lft).each do |project|
252 252 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
253 253 s << "<ul>\n"
254 254 else
255 255 ancestors.pop
256 256 s << "</li>"
257 257 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
258 258 ancestors.pop
259 259 s << "</ul></li>\n"
260 260 end
261 261 end
262 262 s << "<li>"
263 263 s << yield(project).to_s
264 264 ancestors << project
265 265 end
266 266 s << ("</li></ul>\n" * ancestors.size)
267 267 end
268 268 s
269 269 end
270 270
271 271 def principals_check_box_tags(name, principals)
272 272 s = ''
273 273 principals.sort.each do |principal|
274 274 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
275 275 end
276 276 s
277 277 end
278 278
279 279 # Truncates and returns the string as a single line
280 280 def truncate_single_line(string, *args)
281 281 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
282 282 end
283 283
284 284 # Truncates at line break after 250 characters or options[:length]
285 285 def truncate_lines(string, options={})
286 286 length = options[:length] || 250
287 287 if string.to_s =~ /\A(.{#{length}}.*?)$/m
288 288 "#{$1}..."
289 289 else
290 290 string
291 291 end
292 292 end
293 293
294 294 def html_hours(text)
295 295 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
296 296 end
297 297
298 298 def authoring(created, author, options={})
299 299 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
300 300 end
301 301
302 302 def time_tag(time)
303 303 text = distance_of_time_in_words(Time.now, time)
304 304 if @project
305 305 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
306 306 else
307 307 content_tag('acronym', text, :title => format_time(time))
308 308 end
309 309 end
310 310
311 311 def syntax_highlight(name, content)
312 312 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
313 313 end
314 314
315 315 def to_path_param(path)
316 316 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
317 317 end
318 318
319 319 def pagination_links_full(paginator, count=nil, options={})
320 320 page_param = options.delete(:page_param) || :page
321 321 per_page_links = options.delete(:per_page_links)
322 322 url_param = params.dup
323 323 # don't reuse query params if filters are present
324 324 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
325 325
326 326 html = ''
327 327 if paginator.current.previous
328 328 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
329 329 end
330 330
331 331 html << (pagination_links_each(paginator, options) do |n|
332 332 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
333 333 end || '')
334 334
335 335 if paginator.current.next
336 336 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
337 337 end
338 338
339 339 unless count.nil?
340 340 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
341 341 if per_page_links != false && links = per_page_links(paginator.items_per_page)
342 342 html << " | #{links}"
343 343 end
344 344 end
345 345
346 346 html
347 347 end
348 348
349 349 def per_page_links(selected=nil)
350 350 url_param = params.dup
351 351 url_param.clear if url_param.has_key?(:set_filter)
352 352
353 353 links = Setting.per_page_options_array.collect do |n|
354 354 n == selected ? n : link_to_remote(n, {:update => "content",
355 355 :url => params.dup.merge(:per_page => n),
356 356 :method => :get},
357 357 {:href => url_for(url_param.merge(:per_page => n))})
358 358 end
359 359 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
360 360 end
361 361
362 362 def reorder_links(name, url)
363 363 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
364 364 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
365 365 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
366 366 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
367 367 end
368 368
369 369 def breadcrumb(*args)
370 370 elements = args.flatten
371 371 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
372 372 end
373 373
374 374 def other_formats_links(&block)
375 375 concat('<p class="other-formats">' + l(:label_export_to))
376 376 yield Redmine::Views::OtherFormatsBuilder.new(self)
377 377 concat('</p>')
378 378 end
379 379
380 380 def page_header_title
381 381 if @project.nil? || @project.new_record?
382 382 h(Setting.app_title)
383 383 else
384 384 b = []
385 385 ancestors = (@project.root? ? [] : @project.ancestors.visible)
386 386 if ancestors.any?
387 387 root = ancestors.shift
388 388 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
389 389 if ancestors.size > 2
390 390 b << '&#8230;'
391 391 ancestors = ancestors[-2, 2]
392 392 end
393 393 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
394 394 end
395 395 b << h(@project)
396 396 b.join(' &#187; ')
397 397 end
398 398 end
399 399
400 400 def html_title(*args)
401 401 if args.empty?
402 402 title = []
403 403 title << @project.name if @project
404 404 title += @html_title if @html_title
405 405 title << Setting.app_title
406 406 title.select {|t| !t.blank? }.join(' - ')
407 407 else
408 408 @html_title ||= []
409 409 @html_title += args
410 410 end
411 411 end
412 412
413 413 # Returns the theme, controller name, and action as css classes for the
414 414 # HTML body.
415 415 def body_css_classes
416 416 css = []
417 417 if theme = Redmine::Themes.theme(Setting.ui_theme)
418 418 css << 'theme-' + theme.name
419 419 end
420 420
421 421 css << 'controller-' + params[:controller]
422 422 css << 'action-' + params[:action]
423 423 css.join(' ')
424 424 end
425 425
426 426 def accesskey(s)
427 427 Redmine::AccessKeys.key_for s
428 428 end
429 429
430 430 # Formats text according to system settings.
431 431 # 2 ways to call this method:
432 432 # * with a String: textilizable(text, options)
433 433 # * with an object and one of its attribute: textilizable(issue, :description, options)
434 434 def textilizable(*args)
435 435 options = args.last.is_a?(Hash) ? args.pop : {}
436 436 case args.size
437 437 when 1
438 438 obj = options[:object]
439 439 text = args.shift
440 440 when 2
441 441 obj = args.shift
442 442 attr = args.shift
443 443 text = obj.send(attr).to_s
444 444 else
445 445 raise ArgumentError, 'invalid arguments to textilizable'
446 446 end
447 447 return '' if text.blank?
448 448 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
449 449 only_path = options.delete(:only_path) == false ? false : true
450 450
451 451 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
452 452
453 453 parse_non_pre_blocks(text) do |text|
454 454 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
455 455 send method_name, text, project, obj, attr, only_path, options
456 456 end
457 457 end
458 458 end
459 459
460 460 def parse_non_pre_blocks(text)
461 461 s = StringScanner.new(text)
462 462 tags = []
463 463 parsed = ''
464 464 while !s.eos?
465 465 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
466 466 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
467 467 if tags.empty?
468 468 yield text
469 469 end
470 470 parsed << text
471 471 if tag
472 472 if closing
473 473 if tags.last == tag.downcase
474 474 tags.pop
475 475 end
476 476 else
477 477 tags << tag.downcase
478 478 end
479 479 parsed << full_tag
480 480 end
481 481 end
482 482 # Close any non closing tags
483 483 while tag = tags.pop
484 484 parsed << "</#{tag}>"
485 485 end
486 486 parsed
487 487 end
488 488
489 489 def parse_inline_attachments(text, project, obj, attr, only_path, options)
490 490 # when using an image link, try to use an attachment, if possible
491 491 if options[:attachments] || (obj && obj.respond_to?(:attachments))
492 492 attachments = nil
493 493 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
494 494 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
495 495 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
496 496 # search for the picture in attachments
497 497 if found = attachments.detect { |att| att.filename.downcase == filename }
498 498 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
499 499 desc = found.description.to_s.gsub('"', '')
500 500 if !desc.blank? && alttext.blank?
501 501 alt = " title=\"#{desc}\" alt=\"#{desc}\""
502 502 end
503 503 "src=\"#{image_url}\"#{alt}"
504 504 else
505 505 m
506 506 end
507 507 end
508 508 end
509 509 end
510 510
511 511 # Wiki links
512 512 #
513 513 # Examples:
514 514 # [[mypage]]
515 515 # [[mypage|mytext]]
516 516 # wiki links can refer other project wikis, using project name or identifier:
517 517 # [[project:]] -> wiki starting page
518 518 # [[project:|mytext]]
519 519 # [[project:mypage]]
520 520 # [[project:mypage|mytext]]
521 521 def parse_wiki_links(text, project, obj, attr, only_path, options)
522 522 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
523 523 link_project = project
524 524 esc, all, page, title = $1, $2, $3, $5
525 525 if esc.nil?
526 526 if page =~ /^([^\:]+)\:(.*)$/
527 527 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
528 528 page = $2
529 529 title ||= $1 if page.blank?
530 530 end
531 531
532 532 if link_project && link_project.wiki
533 533 # extract anchor
534 534 anchor = nil
535 535 if page =~ /^(.+?)\#(.+)$/
536 536 page, anchor = $1, $2
537 537 end
538 538 # check if page exists
539 539 wiki_page = link_project.wiki.find_page(page)
540 540 url = case options[:wiki_links]
541 541 when :local; "#{title}.html"
542 542 when :anchor; "##{title}" # used for single-file wiki export
543 543 else
544 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => Wiki.titleize(page), :anchor => anchor)
544 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
545 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
545 546 end
546 547 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
547 548 else
548 549 # project or wiki doesn't exist
549 550 all
550 551 end
551 552 else
552 553 all
553 554 end
554 555 end
555 556 end
556 557
557 558 # Redmine links
558 559 #
559 560 # Examples:
560 561 # Issues:
561 562 # #52 -> Link to issue #52
562 563 # Changesets:
563 564 # r52 -> Link to revision 52
564 565 # commit:a85130f -> Link to scmid starting with a85130f
565 566 # Documents:
566 567 # document#17 -> Link to document with id 17
567 568 # document:Greetings -> Link to the document with title "Greetings"
568 569 # document:"Some document" -> Link to the document with title "Some document"
569 570 # Versions:
570 571 # version#3 -> Link to version with id 3
571 572 # version:1.0.0 -> Link to version named "1.0.0"
572 573 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
573 574 # Attachments:
574 575 # attachment:file.zip -> Link to the attachment of the current object named file.zip
575 576 # Source files:
576 577 # source:some/file -> Link to the file located at /some/file in the project's repository
577 578 # source:some/file@52 -> Link to the file's revision 52
578 579 # source:some/file#L120 -> Link to line 120 of the file
579 580 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
580 581 # export:some/file -> Force the download of the file
581 582 # Forum messages:
582 583 # message#1218 -> Link to message with id 1218
583 584 def parse_redmine_links(text, project, obj, attr, only_path, options)
584 585 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
585 586 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
586 587 link = nil
587 588 if esc.nil?
588 589 if prefix.nil? && sep == 'r'
589 590 if project && (changeset = project.changesets.find_by_revision(identifier))
590 591 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
591 592 :class => 'changeset',
592 593 :title => truncate_single_line(changeset.comments, :length => 100))
593 594 end
594 595 elsif sep == '#'
595 596 oid = identifier.to_i
596 597 case prefix
597 598 when nil
598 599 if issue = Issue.visible.find_by_id(oid, :include => :status)
599 600 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
600 601 :class => issue.css_classes,
601 602 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
602 603 end
603 604 when 'document'
604 605 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
605 606 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
606 607 :class => 'document'
607 608 end
608 609 when 'version'
609 610 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
610 611 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
611 612 :class => 'version'
612 613 end
613 614 when 'message'
614 615 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
615 616 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
616 617 :controller => 'messages',
617 618 :action => 'show',
618 619 :board_id => message.board,
619 620 :id => message.root,
620 621 :anchor => (message.parent ? "message-#{message.id}" : nil)},
621 622 :class => 'message'
622 623 end
623 624 when 'project'
624 625 if p = Project.visible.find_by_id(oid)
625 626 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
626 627 end
627 628 end
628 629 elsif sep == ':'
629 630 # removes the double quotes if any
630 631 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
631 632 case prefix
632 633 when 'document'
633 634 if project && document = project.documents.find_by_title(name)
634 635 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
635 636 :class => 'document'
636 637 end
637 638 when 'version'
638 639 if project && version = project.versions.find_by_name(name)
639 640 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
640 641 :class => 'version'
641 642 end
642 643 when 'commit'
643 644 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
644 645 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
645 646 :class => 'changeset',
646 647 :title => truncate_single_line(changeset.comments, :length => 100)
647 648 end
648 649 when 'source', 'export'
649 650 if project && project.repository
650 651 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
651 652 path, rev, anchor = $1, $3, $5
652 653 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
653 654 :path => to_path_param(path),
654 655 :rev => rev,
655 656 :anchor => anchor,
656 657 :format => (prefix == 'export' ? 'raw' : nil)},
657 658 :class => (prefix == 'export' ? 'source download' : 'source')
658 659 end
659 660 when 'attachment'
660 661 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
661 662 if attachments && attachment = attachments.detect {|a| a.filename == name }
662 663 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
663 664 :class => 'attachment'
664 665 end
665 666 when 'project'
666 667 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
667 668 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
668 669 end
669 670 end
670 671 end
671 672 end
672 673 leading + (link || "#{prefix}#{sep}#{identifier}")
673 674 end
674 675 end
675 676
676 677 # Same as Rails' simple_format helper without using paragraphs
677 678 def simple_format_without_paragraph(text)
678 679 text.to_s.
679 680 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
680 681 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
681 682 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
682 683 end
683 684
684 685 def lang_options_for_select(blank=true)
685 686 (blank ? [["(auto)", ""]] : []) +
686 687 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
687 688 end
688 689
689 690 def label_tag_for(name, option_tags = nil, options = {})
690 691 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
691 692 content_tag("label", label_text)
692 693 end
693 694
694 695 def labelled_tabular_form_for(name, object, options, &proc)
695 696 options[:html] ||= {}
696 697 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
697 698 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
698 699 end
699 700
700 701 def back_url_hidden_field_tag
701 702 back_url = params[:back_url] || request.env['HTTP_REFERER']
702 703 back_url = CGI.unescape(back_url.to_s)
703 704 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
704 705 end
705 706
706 707 def check_all_links(form_name)
707 708 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
708 709 " | " +
709 710 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
710 711 end
711 712
712 713 def progress_bar(pcts, options={})
713 714 pcts = [pcts, pcts] unless pcts.is_a?(Array)
714 715 pcts = pcts.collect(&:round)
715 716 pcts[1] = pcts[1] - pcts[0]
716 717 pcts << (100 - pcts[1] - pcts[0])
717 718 width = options[:width] || '100px;'
718 719 legend = options[:legend] || ''
719 720 content_tag('table',
720 721 content_tag('tr',
721 722 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
722 723 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
723 724 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
724 725 ), :class => 'progress', :style => "width: #{width};") +
725 726 content_tag('p', legend, :class => 'pourcent')
726 727 end
727 728
728 729 def checked_image(checked=true)
729 730 if checked
730 731 image_tag 'toggle_check.png'
731 732 end
732 733 end
733 734
734 735 def context_menu(url)
735 736 unless @context_menu_included
736 737 content_for :header_tags do
737 738 javascript_include_tag('context_menu') +
738 739 stylesheet_link_tag('context_menu')
739 740 end
740 741 if l(:direction) == 'rtl'
741 742 content_for :header_tags do
742 743 stylesheet_link_tag('context_menu_rtl')
743 744 end
744 745 end
745 746 @context_menu_included = true
746 747 end
747 748 javascript_tag "new ContextMenu('#{ url_for(url) }')"
748 749 end
749 750
750 751 def context_menu_link(name, url, options={})
751 752 options[:class] ||= ''
752 753 if options.delete(:selected)
753 754 options[:class] << ' icon-checked disabled'
754 755 options[:disabled] = true
755 756 end
756 757 if options.delete(:disabled)
757 758 options.delete(:method)
758 759 options.delete(:confirm)
759 760 options.delete(:onclick)
760 761 options[:class] << ' disabled'
761 762 url = '#'
762 763 end
763 764 link_to name, url, options
764 765 end
765 766
766 767 def calendar_for(field_id)
767 768 include_calendar_headers_tags
768 769 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
769 770 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
770 771 end
771 772
772 773 def include_calendar_headers_tags
773 774 unless @calendar_headers_tags_included
774 775 @calendar_headers_tags_included = true
775 776 content_for :header_tags do
776 777 start_of_week = case Setting.start_of_week.to_i
777 778 when 1
778 779 'Calendar._FD = 1;' # Monday
779 780 when 7
780 781 'Calendar._FD = 0;' # Sunday
781 782 else
782 783 '' # use language
783 784 end
784 785
785 786 javascript_include_tag('calendar/calendar') +
786 787 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
787 788 javascript_tag(start_of_week) +
788 789 javascript_include_tag('calendar/calendar-setup') +
789 790 stylesheet_link_tag('calendar')
790 791 end
791 792 end
792 793 end
793 794
794 795 def content_for(name, content = nil, &block)
795 796 @has_content ||= {}
796 797 @has_content[name] = true
797 798 super(name, content, &block)
798 799 end
799 800
800 801 def has_content?(name)
801 802 (@has_content && @has_content[name]) || false
802 803 end
803 804
804 805 # Returns the avatar image tag for the given +user+ if avatars are enabled
805 806 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
806 807 def avatar(user, options = { })
807 808 if Setting.gravatar_enabled?
808 809 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
809 810 email = nil
810 811 if user.respond_to?(:mail)
811 812 email = user.mail
812 813 elsif user.to_s =~ %r{<(.+?)>}
813 814 email = $1
814 815 end
815 816 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
816 817 else
817 818 ''
818 819 end
819 820 end
820 821
821 822 def favicon
822 823 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
823 824 end
824 825
825 826 private
826 827
827 828 def wiki_helper
828 829 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
829 830 extend helper
830 831 return self
831 832 end
832 833
833 834 def link_to_remote_content_update(text, url_params)
834 835 link_to_remote(text,
835 836 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
836 837 {:href => url_for(:params => url_params)}
837 838 )
838 839 end
839 840
840 841 end
@@ -1,41 +1,41
1 1 <% if @project.shared_versions.any? %>
2 2 <table class="list versions">
3 3 <thead><tr>
4 4 <th><%= l(:label_version) %></th>
5 5 <th><%= l(:field_effective_date) %></th>
6 6 <th><%= l(:field_description) %></th>
7 7 <th><%= l(:field_status) %></th>
8 8 <th><%= l(:field_sharing) %></th>
9 9 <th><%= l(:label_wiki_page) unless @project.wiki.nil? %></th>
10 10 <th style="width:15%"></th>
11 11 </tr></thead>
12 12 <tbody>
13 13 <% for version in @project.shared_versions.sort %>
14 14 <tr class="version <%= cycle 'odd', 'even' %> <%=h version.status %> <%= 'shared' if version.project != @project %>">
15 15 <td class="name"><%= link_to_version version %></td>
16 16 <td class="date"><%= format_date(version.effective_date) %></td>
17 17 <td class="description"><%=h version.description %></td>
18 18 <td class="status"><%= l("version_status_#{version.status}") %></td>
19 19 <td class="sharing"><%=h format_version_sharing(version.sharing) %></td>
20 <td><%= link_to(h(version.wiki_page_title), :controller => 'wiki', :id => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td>
20 <td><%= link_to(h(version.wiki_page_title), :controller => 'wiki', :action => 'show', :id => Wiki.titleize(version.wiki_page_title), :project_id => @project) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td>
21 21 <td class="buttons">
22 22 <% if version.project == @project %>
23 23 <%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %>
24 24 <%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %>
25 25 <% end %>
26 26 </td>
27 27 </tr>
28 28 <% end; reset_cycle %>
29 29 </tbody>
30 30 </table>
31 31 <% else %>
32 32 <p class="nodata"><%= l(:label_no_data) %></p>
33 33 <% end %>
34 34
35 35 <div class="contextual">
36 36 <% if @project.versions.any? %>
37 37 <%= link_to l(:label_close_versions), close_completed_project_versions_path(@project), :method => :put %>
38 38 <% end %>
39 39 </div>
40 40
41 41 <p><%= link_to_if_authorized l(:label_version_new), :controller => 'versions', :action => 'new', :project_id => @project %></p>
@@ -1,33 +1,33
1 1 <div class="contextual">
2 2 <%= watcher_tag(@wiki, User.current) %>
3 3 </div>
4 4
5 5 <h2><%= l(:label_index_by_date) %></h2>
6 6
7 7 <% if @pages.empty? %>
8 8 <p class="nodata"><%= l(:label_no_data) %></p>
9 9 <% end %>
10 10
11 11 <% @pages_by_date.keys.sort.reverse.each do |date| %>
12 12 <h3><%= format_date(date) %></h3>
13 13 <ul>
14 14 <% @pages_by_date[date].each do |page| %>
15 <li><%= link_to page.pretty_title, :action => 'show', :id => page.title %></li>
15 <li><%= link_to page.pretty_title, :action => 'show', :id => page.title, :project_id => page.project %></li>
16 16 <% end %>
17 17 </ul>
18 18 <% end %>
19 19
20 20 <% content_for :sidebar do %>
21 21 <%= render :partial => 'sidebar' %>
22 22 <% end %>
23 23
24 24 <% unless @pages.empty? %>
25 25 <% other_formats_links do |f| %>
26 26 <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'show', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %>
27 27 <%= f.link_to('HTML', :url => {:action => 'export'}) if User.current.allowed_to?(:export_wiki_pages, @project) %>
28 28 <% end %>
29 29 <% end %>
30 30
31 31 <% content_for :header_tags do %>
32 32 <%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'show', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %>
33 33 <% end %>
@@ -1,28 +1,28
1 1 <h2><%=h @page.pretty_title %></h2>
2 2
3 <% form_for :content, @content, :url => {:action => 'update', :id => @page.title}, :html => {:multipart => true, :id => 'wiki_form'} do |f| %>
3 <% form_for :content, @content, :url => {:action => 'update', :id => @page.title}, :html => {:method => :put, :multipart => true, :id => 'wiki_form'} do |f| %>
4 4 <%= f.hidden_field :version %>
5 5 <%= error_messages_for 'content' %>
6 6
7 7 <p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %></p>
8 8 <p><label><%= l(:field_comments) %></label><br /><%= f.text_field :comments, :size => 120 %></p>
9 9 <p><label><%=l(:label_attachment_plural)%></label><br /><%= render :partial => 'attachments/form' %></p>
10 10
11 11 <p><%= submit_tag l(:button_save) %>
12 12 <%= link_to_remote l(:label_preview),
13 13 { :url => { :controller => 'wiki', :action => 'preview', :project_id => @project, :id => @page.title },
14 :method => 'post',
14 :method => :post,
15 15 :update => 'preview',
16 16 :with => "Form.serialize('wiki_form')",
17 17 :complete => "Element.scrollTo('preview')"
18 18 }, :accesskey => accesskey(:preview) %></p>
19 19 <%= wikitoolbar_for 'content_text' %>
20 20 <% end %>
21 21
22 22 <div id="preview" class="wiki"></div>
23 23
24 24 <% content_for :header_tags do %>
25 25 <%= stylesheet_link_tag 'scm' %>
26 26 <% end %>
27 27
28 28 <% html_title @page.pretty_title %>
@@ -1,36 +1,36
1 1 <h2><%= @page.pretty_title %></h2>
2 2
3 3 <h3><%= l(:label_history) %></h3>
4 4
5 5 <% form_tag({:action => "diff"}, :method => :get) do %>
6 6 <%= hidden_field_tag('project_id', h(@project.to_param)) %>
7 7 <table class="list">
8 8 <thead><tr>
9 9 <th>#</th>
10 10 <th></th>
11 11 <th></th>
12 12 <th><%= l(:field_updated_on) %></th>
13 13 <th><%= l(:field_author) %></th>
14 14 <th><%= l(:field_comments) %></th>
15 15 <th></th>
16 16 </tr></thead>
17 17 <tbody>
18 18 <% show_diff = @versions.size > 1 %>
19 19 <% line_num = 1 %>
20 20 <% @versions.each do |ver| %>
21 21 <tr class="<%= cycle("odd", "even") %>">
22 <td class="id"><%= link_to ver.version, :action => 'show', :id => @page.title, :version => ver.version %></td>
22 <td class="id"><%= link_to ver.version, :action => 'show', :id => @page.title, :project_id => @page.project, :version => ver.version %></td>
23 23 <td class="checkbox"><%= radio_button_tag('version', ver.version, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < @versions.size) %></td>
24 24 <td class="checkbox"><%= radio_button_tag('version_from', ver.version, (line_num==2), :id => "cbto-#{line_num}") if show_diff && (line_num > 1) %></td>
25 25 <td align="center"><%= format_time(ver.updated_on) %></td>
26 26 <td><%= link_to_user ver.author %></td>
27 27 <td><%=h ver.comments %></td>
28 28 <td align="center"><%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %></td>
29 29 </tr>
30 30 <% line_num += 1 %>
31 31 <% end %>
32 32 </tbody>
33 33 </table>
34 34 <%= submit_tag l(:label_view_diff), :class => 'small' if show_diff %>
35 35 <span class="pagination"><%= pagination_links_full @version_pages, @version_count, :page_param => :p %></span>
36 36 <% end %>
@@ -1,61 +1,61
1 1 <div class="contextual">
2 2 <% if @editable %>
3 3 <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if @content.version == @page.content.version %>
4 4 <%= watcher_tag(@page, User.current) %>
5 5 <%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
6 6 <%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
7 7 <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') if @content.version == @page.content.version %>
8 8 <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page.title}, :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
9 9 <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page.title, :version => @content.version }, :class => 'icon icon-cancel') if @content.version < @page.content.version %>
10 10 <% end %>
11 11 <%= link_to_if_authorized(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history') %>
12 12 </div>
13 13
14 <%= breadcrumb(@page.ancestors.reverse.collect {|parent| link_to h(parent.pretty_title), {:id => parent.title}}) %>
14 <%= breadcrumb(@page.ancestors.reverse.collect {|parent| link_to h(parent.pretty_title), {:id => parent.title, :project_id => parent.project}}) %>
15 15
16 16 <% if @content.version != @page.content.version %>
17 17 <p>
18 <%= link_to(('&#171; ' + l(:label_previous)), :action => 'show', :id => @page.title, :version => (@content.version - 1)) + " - " if @content.version > 1 %>
18 <%= link_to(('&#171; ' + l(:label_previous)), :action => 'show', :id => @page.title, :project_id => @page.project, :version => (@content.version - 1)) + " - " if @content.version > 1 %>
19 19 <%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %>
20 <%= '(' + link_to('diff', :controller => 'wiki', :action => 'diff', :id => @page.title, :version => @content.version) + ')' if @content.version > 1 %> -
21 <%= link_to((l(:label_next) + ' &#187;'), :action => 'show', :id => @page.title, :version => (@content.version + 1)) + " - " if @content.version < @page.content.version %>
22 <%= link_to(l(:label_current_version), :action => 'show', :id => @page.title) %>
20 <%= '(' + link_to('diff', :controller => 'wiki', :action => 'diff', :id => @page.title, :project_id => @page.project, :version => @content.version) + ')' if @content.version > 1 %> -
21 <%= link_to((l(:label_next) + ' &#187;'), :action => 'show', :id => @page.title, :project_id => @page.project, :version => (@content.version + 1)) + " - " if @content.version < @page.content.version %>
22 <%= link_to(l(:label_current_version), :action => 'show', :id => @page.title, :project_id => @page.project) %>
23 23 <br />
24 24 <em><%= @content.author ? @content.author.name : "anonyme" %>, <%= format_time(@content.updated_on) %> </em><br />
25 25 <%=h @content.comments %>
26 26 </p>
27 27 <hr />
28 28 <% end %>
29 29
30 30 <%= render(:partial => "wiki/content", :locals => {:content => @content}) %>
31 31
32 32 <%= link_to_attachments @page %>
33 33
34 34 <% if @editable && authorize_for('wiki', 'add_attachment') %>
35 35 <div id="wiki_add_attachment">
36 36 <p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;",
37 37 :id => 'attach_files_link' %></p>
38 38 <% form_tag({ :controller => 'wiki', :action => 'add_attachment', :project_id => @project, :id => @page.title }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %>
39 39 <div class="box">
40 40 <p><%= render :partial => 'attachments/form' %></p>
41 41 </div>
42 42 <%= submit_tag l(:button_add) %>
43 43 <%= link_to l(:button_cancel), {}, :onclick => "Element.hide('add_attachment_form'); Element.show('attach_files_link'); return false;" %>
44 44 <% end %>
45 45 </div>
46 46 <% end %>
47 47
48 48 <% other_formats_links do |f| %>
49 49 <%= f.link_to 'HTML', :url => {:id => @page.title, :version => @content.version} %>
50 50 <%= f.link_to 'TXT', :url => {:id => @page.title, :version => @content.version} %>
51 51 <% end if User.current.allowed_to?(:export_wiki_pages, @project) %>
52 52
53 53 <% content_for :header_tags do %>
54 54 <%= stylesheet_link_tag 'scm' %>
55 55 <% end %>
56 56
57 57 <% content_for :sidebar do %>
58 58 <%= render :partial => 'sidebar' %>
59 59 <% end %>
60 60
61 61 <% html_title @page.pretty_title %>
@@ -1,252 +1,245
1 1 ActionController::Routing::Routes.draw do |map|
2 2 # Add your own custom routes here.
3 3 # The priority is based upon order of creation: first created -> highest priority.
4 4
5 5 # Here's a sample route:
6 6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 7 # Keep in mind you can assign values other than :controller and :action
8 8
9 9 map.home '', :controller => 'welcome'
10 10
11 11 map.signin 'login', :controller => 'account', :action => 'login'
12 12 map.signout 'logout', :controller => 'account', :action => 'logout'
13 13
14 14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 15 map.connect 'help/:ctrl/:page', :controller => 'help'
16 16
17 17 map.connect 'projects/:project_id/time_entries/report', :controller => 'time_entry_reports', :action => 'report'
18 18 map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report|
19 19 time_report.connect 'time_entries/report'
20 20 time_report.connect 'time_entries/report.:format'
21 21 time_report.connect 'projects/:project_id/time_entries/report.:format'
22 22 end
23 23
24 24 # TODO: wasteful since this is also nested under issues, projects, and projects/issues
25 25 map.resources :time_entries, :controller => 'timelog'
26 26
27 27 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
28 28 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
29 29 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
30 map.with_options :controller => 'wiki' do |wiki_routes|
31 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
32 wiki_views.connect 'projects/:project_id/wiki/export', :action => 'export'
33 wiki_views.connect 'projects/:project_id/wiki/index', :action => 'index'
34 wiki_views.connect 'projects/:project_id/wiki/date_index', :action => 'date_index'
35 wiki_views.connect 'projects/:project_id/wiki/:id', :action => 'show', :id => nil
36 wiki_views.connect 'projects/:project_id/wiki/:id/edit', :action => 'edit'
37 wiki_views.connect 'projects/:project_id/wiki/:id/rename', :action => 'rename'
38 wiki_views.connect 'projects/:project_id/wiki/:id/history', :action => 'history'
39 wiki_views.connect 'projects/:project_id/wiki/:id/diff/:version/vs/:version_from', :action => 'diff'
40 wiki_views.connect 'projects/:project_id/wiki/:id/annotate/:version', :action => 'annotate'
41 end
42
43 wiki_routes.connect 'projects/:project_id/wiki/:id/:action',
44 :action => /rename|preview|protect|add_attachment/,
45 :conditions => {:method => :post}
46
47 wiki_routes.connect 'projects/:project_id/wiki/:id/edit', :action => 'update', :conditions => {:method => :post}
48
49 wiki_routes.connect 'projects/:project_id/wiki/:id', :action => 'destroy', :conditions => {:method => :delete}
50 end
51 30
52 31 map.with_options :controller => 'messages' do |messages_routes|
53 32 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
54 33 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
55 34 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
56 35 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
57 36 end
58 37 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
59 38 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
60 39 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
61 40 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
62 41 end
63 42 end
64 43
65 44 map.with_options :controller => 'boards' do |board_routes|
66 45 board_routes.with_options :conditions => {:method => :get} do |board_views|
67 46 board_views.connect 'projects/:project_id/boards', :action => 'index'
68 47 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
69 48 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
70 49 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
71 50 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
72 51 end
73 52 board_routes.with_options :conditions => {:method => :post} do |board_actions|
74 53 board_actions.connect 'projects/:project_id/boards', :action => 'new'
75 54 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
76 55 end
77 56 end
78 57
79 58 map.with_options :controller => 'documents' do |document_routes|
80 59 document_routes.with_options :conditions => {:method => :get} do |document_views|
81 60 document_views.connect 'projects/:project_id/documents', :action => 'index'
82 61 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
83 62 document_views.connect 'documents/:id', :action => 'show'
84 63 document_views.connect 'documents/:id/edit', :action => 'edit'
85 64 end
86 65 document_routes.with_options :conditions => {:method => :post} do |document_actions|
87 66 document_actions.connect 'projects/:project_id/documents', :action => 'new'
88 67 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
89 68 end
90 69 end
91 70
92 71 map.resources :issue_moves, :only => [:new, :create], :path_prefix => '/issues', :as => 'move'
93 72
94 73 # Misc issue routes. TODO: move into resources
95 74 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues'
96 75 map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' # TODO: would look nicer as /issues/:id/preview
97 76 map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues'
98 77 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
99 78 map.bulk_edit_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_edit', :conditions => { :method => :get }
100 79 map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
101 80 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
102 81 map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
103 82
104 83 map.resource :gantt, :path_prefix => '/issues', :controller => 'gantts', :only => [:show, :update]
105 84 map.resource :gantt, :path_prefix => '/projects/:project_id/issues', :controller => 'gantts', :only => [:show, :update]
106 85 map.resource :calendar, :path_prefix => '/issues', :controller => 'calendars', :only => [:show, :update]
107 86 map.resource :calendar, :path_prefix => '/projects/:project_id/issues', :controller => 'calendars', :only => [:show, :update]
108 87
109 88 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
110 89 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
111 90 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
112 91 end
113 92
114 93 # Following two routes conflict with the resources because #index allows POST
115 94 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
116 95 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
117 96
118 97 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
119 98 issues.resources :time_entries, :controller => 'timelog'
120 99 end
121 100
122 101 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
123 102 issues.resources :time_entries, :controller => 'timelog'
124 103 end
125 104
126 105 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
127 106 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
128 107 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
129 108 end
130 109
131 110 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
132 111
133 112 map.with_options :controller => 'users' do |users|
134 113 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
135 114
136 115 users.with_options :conditions => {:method => :post} do |user_actions|
137 116 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
138 117 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
139 118 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
140 119 end
141 120 end
142 121
143 122 map.resources :users, :member => {
144 123 :edit_membership => :post,
145 124 :destroy_membership => :post
146 125 },
147 126 :except => [:destroy]
148 127
149 128 # For nice "roadmap" in the url for the index action
150 129 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
151 130
152 131 map.all_news 'news', :controller => 'news', :action => 'index'
153 132 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
154 133 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
155 134 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
156 135 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
157 136
158 137 map.resources :projects, :member => {
159 138 :copy => [:get, :post],
160 139 :settings => :get,
161 140 :modules => :post,
162 141 :archive => :post,
163 142 :unarchive => :post
164 143 } do |project|
165 144 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
166 145 project.resources :files, :only => [:index, :new, :create]
167 146 project.resources :versions, :collection => {:close_completed => :put}, :member => {:status_by => :post}
168 147 project.resources :news, :shallow => true
169 148 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
170 149
171
150 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
151 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
152 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
153 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
154 project.resources :wiki, :except => [:new, :create], :member => {
155 :rename => [:get, :post],
156 :history => :get,
157 :preview => :any,
158 :protect => :post,
159 :add_attachment => :post
160 }, :collection => {
161 :export => :get,
162 :date_index => :get
163 }
164
172 165 end
173 166
174 167 # Destroy uses a get request to prompt the user before the actual DELETE request
175 168 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
176 169
177 170 # TODO: port to be part of the resources route(s)
178 171 map.with_options :controller => 'projects' do |project_mapper|
179 172 project_mapper.with_options :conditions => {:method => :get} do |project_views|
180 173 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
181 174 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
182 175 end
183 176 end
184 177
185 178 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
186 179 activity.connect 'projects/:id/activity'
187 180 activity.connect 'projects/:id/activity.:format'
188 181 activity.connect 'activity', :id => nil
189 182 activity.connect 'activity.:format', :id => nil
190 183 end
191 184
192 185
193 186 map.with_options :controller => 'issue_categories' do |categories|
194 187 categories.connect 'projects/:project_id/issue_categories/new', :action => 'new'
195 188 end
196 189
197 190 map.with_options :controller => 'repositories' do |repositories|
198 191 repositories.with_options :conditions => {:method => :get} do |repository_views|
199 192 repository_views.connect 'projects/:id/repository', :action => 'show'
200 193 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
201 194 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
202 195 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
203 196 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
204 197 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
205 198 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
206 199 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
207 200 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
208 201 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
209 202 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
210 203 # TODO: why the following route is required?
211 204 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
212 205 repository_views.connect 'projects/:id/repository/:action/*path'
213 206 end
214 207
215 208 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
216 209 end
217 210
218 211 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
219 212 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
220 213 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
221 214
222 215 map.resources :groups
223 216
224 217 #left old routes at the bottom for backwards compat
225 218 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
226 219 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
227 220 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
228 221 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
229 222 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
230 223 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
231 224 map.connect 'projects/:project_id/news/:action', :controller => 'news'
232 225 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
233 226 map.with_options :controller => 'repositories' do |omap|
234 227 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
235 228 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
236 229 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
237 230 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
238 231 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
239 232 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
240 233 end
241 234
242 235 map.with_options :controller => 'sys' do |sys|
243 236 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
244 237 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
245 238 end
246 239
247 240 # Install the default route as the lowest priority.
248 241 map.connect ':controller/:action/:id'
249 242 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
250 243 # Used for OpenID
251 244 map.root :controller => 'account', :action => 'login'
252 245 end
@@ -1,365 +1,365
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19 require 'wiki_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class WikiController; def rescue_action(e) raise e end; end
23 23
24 24 class WikiControllerTest < ActionController::TestCase
25 25 fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :attachments
26 26
27 27 def setup
28 28 @controller = WikiController.new
29 29 @request = ActionController::TestRequest.new
30 30 @response = ActionController::TestResponse.new
31 31 User.current = nil
32 32 end
33 33
34 34 def test_show_start_page
35 35 get :show, :project_id => 'ecookbook'
36 36 assert_response :success
37 37 assert_template 'show'
38 38 assert_tag :tag => 'h1', :content => /CookBook documentation/
39 39
40 40 # child_pages macro
41 41 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
42 42 :child => { :tag => 'li',
43 43 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
44 44 :content => 'Page with an inline image' } }
45 45 end
46 46
47 47 def test_show_page_with_name
48 48 get :show, :project_id => 1, :id => 'Another_page'
49 49 assert_response :success
50 50 assert_template 'show'
51 51 assert_tag :tag => 'h1', :content => /Another page/
52 52 # Included page with an inline image
53 53 assert_tag :tag => 'p', :content => /This is an inline image/
54 54 assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3',
55 55 :alt => 'This is a logo' }
56 56 end
57 57
58 58 def test_show_with_sidebar
59 59 page = Project.find(1).wiki.pages.new(:title => 'Sidebar')
60 60 page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar')
61 61 page.save!
62 62
63 63 get :show, :project_id => 1, :id => 'Another_page'
64 64 assert_response :success
65 65 assert_tag :tag => 'div', :attributes => {:id => 'sidebar'},
66 66 :content => /Side bar content for test_show_with_sidebar/
67 67 end
68 68
69 69 def test_show_unexistent_page_without_edit_right
70 70 get :show, :project_id => 1, :id => 'Unexistent page'
71 71 assert_response 404
72 72 end
73 73
74 74 def test_show_unexistent_page_with_edit_right
75 75 @request.session[:user_id] = 2
76 76 get :show, :project_id => 1, :id => 'Unexistent page'
77 77 assert_response :success
78 78 assert_template 'edit'
79 79 end
80 80
81 81 def test_create_page
82 82 @request.session[:user_id] = 2
83 post :update, :project_id => 1,
83 put :update, :project_id => 1,
84 84 :id => 'New page',
85 85 :content => {:comments => 'Created the page',
86 86 :text => "h1. New page\n\nThis is a new page",
87 87 :version => 0}
88 88 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
89 89 page = Project.find(1).wiki.find_page('New page')
90 90 assert !page.new_record?
91 91 assert_not_nil page.content
92 92 assert_equal 'Created the page', page.content.comments
93 93 end
94 94
95 95 def test_create_page_with_attachments
96 96 @request.session[:user_id] = 2
97 97 assert_difference 'WikiPage.count' do
98 98 assert_difference 'Attachment.count' do
99 post :update, :project_id => 1,
99 put :update, :project_id => 1,
100 100 :id => 'New page',
101 101 :content => {:comments => 'Created the page',
102 102 :text => "h1. New page\n\nThis is a new page",
103 103 :version => 0},
104 104 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
105 105 end
106 106 end
107 107 page = Project.find(1).wiki.find_page('New page')
108 108 assert_equal 1, page.attachments.count
109 109 assert_equal 'testfile.txt', page.attachments.first.filename
110 110 end
111 111
112 112 def test_preview
113 113 @request.session[:user_id] = 2
114 114 xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation',
115 115 :content => { :comments => '',
116 116 :text => 'this is a *previewed text*',
117 117 :version => 3 }
118 118 assert_response :success
119 119 assert_template 'common/_preview'
120 120 assert_tag :tag => 'strong', :content => /previewed text/
121 121 end
122 122
123 123 def test_preview_new_page
124 124 @request.session[:user_id] = 2
125 125 xhr :post, :preview, :project_id => 1, :id => 'New page',
126 126 :content => { :text => 'h1. New page',
127 127 :comments => '',
128 128 :version => 0 }
129 129 assert_response :success
130 130 assert_template 'common/_preview'
131 131 assert_tag :tag => 'h1', :content => /New page/
132 132 end
133 133
134 134 def test_history
135 135 get :history, :project_id => 1, :id => 'CookBook_documentation'
136 136 assert_response :success
137 137 assert_template 'history'
138 138 assert_not_nil assigns(:versions)
139 139 assert_equal 3, assigns(:versions).size
140 140 assert_select "input[type=submit][name=commit]"
141 141 end
142 142
143 143 def test_history_with_one_version
144 144 get :history, :project_id => 1, :id => 'Another_page'
145 145 assert_response :success
146 146 assert_template 'history'
147 147 assert_not_nil assigns(:versions)
148 148 assert_equal 1, assigns(:versions).size
149 149 assert_select "input[type=submit][name=commit]", false
150 150 end
151 151
152 152 def test_diff
153 153 get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => 2, :version_from => 1
154 154 assert_response :success
155 155 assert_template 'diff'
156 156 assert_tag :tag => 'span', :attributes => { :class => 'diff_in'},
157 157 :content => /updated/
158 158 end
159 159
160 160 def test_annotate
161 161 get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => 2
162 162 assert_response :success
163 163 assert_template 'annotate'
164 164 # Line 1
165 165 assert_tag :tag => 'tr', :child => { :tag => 'th', :attributes => {:class => 'line-num'}, :content => '1' },
166 166 :child => { :tag => 'td', :attributes => {:class => 'author'}, :content => /John Smith/ },
167 167 :child => { :tag => 'td', :content => /h1\. CookBook documentation/ }
168 168 # Line 2
169 169 assert_tag :tag => 'tr', :child => { :tag => 'th', :attributes => {:class => 'line-num'}, :content => '2' },
170 170 :child => { :tag => 'td', :attributes => {:class => 'author'}, :content => /redMine Admin/ },
171 171 :child => { :tag => 'td', :content => /Some updated \[\[documentation\]\] here/ }
172 172 end
173 173
174 174 def test_rename_with_redirect
175 175 @request.session[:user_id] = 2
176 176 post :rename, :project_id => 1, :id => 'Another_page',
177 177 :wiki_page => { :title => 'Another renamed page',
178 178 :redirect_existing_links => 1 }
179 179 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
180 180 wiki = Project.find(1).wiki
181 181 # Check redirects
182 182 assert_not_nil wiki.find_page('Another page')
183 183 assert_nil wiki.find_page('Another page', :with_redirect => false)
184 184 end
185 185
186 186 def test_rename_without_redirect
187 187 @request.session[:user_id] = 2
188 188 post :rename, :project_id => 1, :id => 'Another_page',
189 189 :wiki_page => { :title => 'Another renamed page',
190 190 :redirect_existing_links => "0" }
191 191 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
192 192 wiki = Project.find(1).wiki
193 193 # Check that there's no redirects
194 194 assert_nil wiki.find_page('Another page')
195 195 end
196 196
197 197 def test_destroy_child
198 198 @request.session[:user_id] = 2
199 199 delete :destroy, :project_id => 1, :id => 'Child_1'
200 200 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
201 201 end
202 202
203 203 def test_destroy_parent
204 204 @request.session[:user_id] = 2
205 205 assert_no_difference('WikiPage.count') do
206 206 delete :destroy, :project_id => 1, :id => 'Another_page'
207 207 end
208 208 assert_response :success
209 209 assert_template 'destroy'
210 210 end
211 211
212 212 def test_destroy_parent_with_nullify
213 213 @request.session[:user_id] = 2
214 214 assert_difference('WikiPage.count', -1) do
215 215 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'nullify'
216 216 end
217 217 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
218 218 assert_nil WikiPage.find_by_id(2)
219 219 end
220 220
221 221 def test_destroy_parent_with_cascade
222 222 @request.session[:user_id] = 2
223 223 assert_difference('WikiPage.count', -3) do
224 224 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'destroy'
225 225 end
226 226 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
227 227 assert_nil WikiPage.find_by_id(2)
228 228 assert_nil WikiPage.find_by_id(5)
229 229 end
230 230
231 231 def test_destroy_parent_with_reassign
232 232 @request.session[:user_id] = 2
233 233 assert_difference('WikiPage.count', -1) do
234 234 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'reassign', :reassign_to_id => 1
235 235 end
236 236 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
237 237 assert_nil WikiPage.find_by_id(2)
238 238 assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent
239 239 end
240 240
241 241 def test_index
242 242 get :index, :project_id => 'ecookbook'
243 243 assert_response :success
244 244 assert_template 'index'
245 245 pages = assigns(:pages)
246 246 assert_not_nil pages
247 247 assert_equal Project.find(1).wiki.pages.size, pages.size
248 248
249 249 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
250 250 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/CookBook_documentation' },
251 251 :content => 'CookBook documentation' },
252 252 :child => { :tag => 'ul',
253 253 :child => { :tag => 'li',
254 254 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
255 255 :content => 'Page with an inline image' } } } },
256 256 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Another_page' },
257 257 :content => 'Another page' } }
258 258 end
259 259
260 260 context "GET :export" do
261 261 context "with an authorized user to export the wiki" do
262 262 setup do
263 263 @request.session[:user_id] = 2
264 264 get :export, :project_id => 'ecookbook'
265 265 end
266 266
267 267 should_respond_with :success
268 268 should_assign_to :pages
269 269 should_respond_with_content_type "text/html"
270 270 should "export all of the wiki pages to a single html file" do
271 271 assert_select "a[name=?]", "CookBook_documentation"
272 272 assert_select "a[name=?]", "Another_page"
273 273 assert_select "a[name=?]", "Page_with_an_inline_image"
274 274 end
275 275
276 276 end
277 277
278 278 context "with an unauthorized user" do
279 279 setup do
280 280 get :export, :project_id => 'ecookbook'
281 281
282 282 should_respond_with :redirect
283 283 should_redirect_to('wiki index') { {:action => 'show', :project_id => @project, :id => nil} }
284 284 end
285 285 end
286 286 end
287 287
288 288 context "GET :date_index" do
289 289 setup do
290 290 get :date_index, :project_id => 'ecookbook'
291 291 end
292 292
293 293 should_respond_with :success
294 294 should_assign_to :pages
295 295 should_assign_to :pages_by_date
296 296 should_render_template 'wiki/date_index'
297 297
298 298 end
299 299
300 300 def test_not_found
301 301 get :show, :project_id => 999
302 302 assert_response 404
303 303 end
304 304
305 305 def test_protect_page
306 306 page = WikiPage.find_by_wiki_id_and_title(1, 'Another_page')
307 307 assert !page.protected?
308 308 @request.session[:user_id] = 2
309 309 post :protect, :project_id => 1, :id => page.title, :protected => '1'
310 310 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
311 311 assert page.reload.protected?
312 312 end
313 313
314 314 def test_unprotect_page
315 315 page = WikiPage.find_by_wiki_id_and_title(1, 'CookBook_documentation')
316 316 assert page.protected?
317 317 @request.session[:user_id] = 2
318 318 post :protect, :project_id => 1, :id => page.title, :protected => '0'
319 319 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'CookBook_documentation'
320 320 assert !page.reload.protected?
321 321 end
322 322
323 323 def test_show_page_with_edit_link
324 324 @request.session[:user_id] = 2
325 325 get :show, :project_id => 1
326 326 assert_response :success
327 327 assert_template 'show'
328 328 assert_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
329 329 end
330 330
331 331 def test_show_page_without_edit_link
332 332 @request.session[:user_id] = 4
333 333 get :show, :project_id => 1
334 334 assert_response :success
335 335 assert_template 'show'
336 336 assert_no_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
337 337 end
338 338
339 339 def test_edit_unprotected_page
340 340 # Non members can edit unprotected wiki pages
341 341 @request.session[:user_id] = 4
342 342 get :edit, :project_id => 1, :id => 'Another_page'
343 343 assert_response :success
344 344 assert_template 'edit'
345 345 end
346 346
347 347 def test_edit_protected_page_by_nonmember
348 348 # Non members can't edit protected wiki pages
349 349 @request.session[:user_id] = 4
350 350 get :edit, :project_id => 1, :id => 'CookBook_documentation'
351 351 assert_response 403
352 352 end
353 353
354 354 def test_edit_protected_page_by_member
355 355 @request.session[:user_id] = 2
356 356 get :edit, :project_id => 1, :id => 'CookBook_documentation'
357 357 assert_response :success
358 358 assert_template 'edit'
359 359 end
360 360
361 361 def test_history_of_non_existing_page_should_return_404
362 362 get :history, :project_id => 1, :id => 'Unknown_page'
363 363 assert_response 404
364 364 end
365 365 end
@@ -1,344 +1,345
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "#{File.dirname(__FILE__)}/../test_helper"
19 19
20 20 class RoutingTest < ActionController::IntegrationTest
21 21 context "activities" do
22 22 should_route :get, "/activity", :controller => 'activities', :action => 'index', :id => nil
23 23 should_route :get, "/activity.atom", :controller => 'activities', :action => 'index', :id => nil, :format => 'atom'
24 24 end
25 25
26 26 context "attachments" do
27 27 should_route :get, "/attachments/1", :controller => 'attachments', :action => 'show', :id => '1'
28 28 should_route :get, "/attachments/1/filename.ext", :controller => 'attachments', :action => 'show', :id => '1', :filename => 'filename.ext'
29 29 should_route :get, "/attachments/download/1", :controller => 'attachments', :action => 'download', :id => '1'
30 30 should_route :get, "/attachments/download/1/filename.ext", :controller => 'attachments', :action => 'download', :id => '1', :filename => 'filename.ext'
31 31 end
32 32
33 33 context "boards" do
34 34 should_route :get, "/projects/world_domination/boards", :controller => 'boards', :action => 'index', :project_id => 'world_domination'
35 35 should_route :get, "/projects/world_domination/boards/new", :controller => 'boards', :action => 'new', :project_id => 'world_domination'
36 36 should_route :get, "/projects/world_domination/boards/44", :controller => 'boards', :action => 'show', :project_id => 'world_domination', :id => '44'
37 37 should_route :get, "/projects/world_domination/boards/44.atom", :controller => 'boards', :action => 'show', :project_id => 'world_domination', :id => '44', :format => 'atom'
38 38 should_route :get, "/projects/world_domination/boards/44/edit", :controller => 'boards', :action => 'edit', :project_id => 'world_domination', :id => '44'
39 39
40 40 should_route :post, "/projects/world_domination/boards/new", :controller => 'boards', :action => 'new', :project_id => 'world_domination'
41 41 should_route :post, "/projects/world_domination/boards/44/edit", :controller => 'boards', :action => 'edit', :project_id => 'world_domination', :id => '44'
42 42 should_route :post, "/projects/world_domination/boards/44/destroy", :controller => 'boards', :action => 'destroy', :project_id => 'world_domination', :id => '44'
43 43
44 44 end
45 45
46 46 context "documents" do
47 47 should_route :get, "/projects/567/documents", :controller => 'documents', :action => 'index', :project_id => '567'
48 48 should_route :get, "/projects/567/documents/new", :controller => 'documents', :action => 'new', :project_id => '567'
49 49 should_route :get, "/documents/22", :controller => 'documents', :action => 'show', :id => '22'
50 50 should_route :get, "/documents/22/edit", :controller => 'documents', :action => 'edit', :id => '22'
51 51
52 52 should_route :post, "/projects/567/documents/new", :controller => 'documents', :action => 'new', :project_id => '567'
53 53 should_route :post, "/documents/567/edit", :controller => 'documents', :action => 'edit', :id => '567'
54 54 should_route :post, "/documents/567/destroy", :controller => 'documents', :action => 'destroy', :id => '567'
55 55 end
56 56
57 57 context "issues" do
58 58 # REST actions
59 59 should_route :get, "/issues", :controller => 'issues', :action => 'index'
60 60 should_route :get, "/issues.pdf", :controller => 'issues', :action => 'index', :format => 'pdf'
61 61 should_route :get, "/issues.atom", :controller => 'issues', :action => 'index', :format => 'atom'
62 62 should_route :get, "/issues.xml", :controller => 'issues', :action => 'index', :format => 'xml'
63 63 should_route :get, "/projects/23/issues", :controller => 'issues', :action => 'index', :project_id => '23'
64 64 should_route :get, "/projects/23/issues.pdf", :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
65 65 should_route :get, "/projects/23/issues.atom", :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
66 66 should_route :get, "/projects/23/issues.xml", :controller => 'issues', :action => 'index', :project_id => '23', :format => 'xml'
67 67 should_route :get, "/issues/64", :controller => 'issues', :action => 'show', :id => '64'
68 68 should_route :get, "/issues/64.pdf", :controller => 'issues', :action => 'show', :id => '64', :format => 'pdf'
69 69 should_route :get, "/issues/64.atom", :controller => 'issues', :action => 'show', :id => '64', :format => 'atom'
70 70 should_route :get, "/issues/64.xml", :controller => 'issues', :action => 'show', :id => '64', :format => 'xml'
71 71
72 72 should_route :get, "/projects/23/issues/new", :controller => 'issues', :action => 'new', :project_id => '23'
73 73 should_route :post, "/projects/23/issues", :controller => 'issues', :action => 'create', :project_id => '23'
74 74 should_route :post, "/issues.xml", :controller => 'issues', :action => 'create', :format => 'xml'
75 75
76 76 should_route :get, "/issues/64/edit", :controller => 'issues', :action => 'edit', :id => '64'
77 77 # TODO: Should use PUT
78 78 should_route :post, "/issues/64/edit", :controller => 'issues', :action => 'edit', :id => '64'
79 79 should_route :put, "/issues/1.xml", :controller => 'issues', :action => 'update', :id => '1', :format => 'xml'
80 80
81 81 # TODO: Should use DELETE
82 82 should_route :post, "/issues/64/destroy", :controller => 'issues', :action => 'destroy', :id => '64'
83 83 should_route :delete, "/issues/1.xml", :controller => 'issues', :action => 'destroy', :id => '1', :format => 'xml'
84 84
85 85 # Extra actions
86 86 should_route :get, "/projects/23/issues/64/copy", :controller => 'issues', :action => 'new', :project_id => '23', :copy_from => '64'
87 87
88 88 should_route :get, "/issues/move/new", :controller => 'issue_moves', :action => 'new'
89 89 should_route :post, "/issues/move", :controller => 'issue_moves', :action => 'create'
90 90
91 91 should_route :post, "/issues/1/quoted", :controller => 'journals', :action => 'new', :id => '1'
92 92
93 93 should_route :get, "/issues/calendar", :controller => 'calendars', :action => 'show'
94 94 should_route :put, "/issues/calendar", :controller => 'calendars', :action => 'update'
95 95 should_route :get, "/projects/project-name/issues/calendar", :controller => 'calendars', :action => 'show', :project_id => 'project-name'
96 96 should_route :put, "/projects/project-name/issues/calendar", :controller => 'calendars', :action => 'update', :project_id => 'project-name'
97 97
98 98 should_route :get, "/issues/gantt", :controller => 'gantts', :action => 'show'
99 99 should_route :put, "/issues/gantt", :controller => 'gantts', :action => 'update'
100 100 should_route :get, "/projects/project-name/issues/gantt", :controller => 'gantts', :action => 'show', :project_id => 'project-name'
101 101 should_route :put, "/projects/project-name/issues/gantt", :controller => 'gantts', :action => 'update', :project_id => 'project-name'
102 102
103 103 should_route :get, "/issues/auto_complete", :controller => 'auto_completes', :action => 'issues'
104 104
105 105 should_route :get, "/issues/preview/123", :controller => 'previews', :action => 'issue', :id => '123'
106 106 should_route :post, "/issues/preview/123", :controller => 'previews', :action => 'issue', :id => '123'
107 107 should_route :get, "/issues/context_menu", :controller => 'context_menus', :action => 'issues'
108 108 should_route :post, "/issues/context_menu", :controller => 'context_menus', :action => 'issues'
109 109
110 110 should_route :get, "/issues/changes", :controller => 'journals', :action => 'index'
111 111
112 112 should_route :get, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_edit'
113 113 should_route :post, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_update'
114 114 end
115 115
116 116 context "issue categories" do
117 117 should_route :get, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
118 118
119 119 should_route :post, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
120 120 end
121 121
122 122 context "issue relations" do
123 123 should_route :post, "/issues/1/relations", :controller => 'issue_relations', :action => 'new', :issue_id => '1'
124 124 should_route :post, "/issues/1/relations/23/destroy", :controller => 'issue_relations', :action => 'destroy', :issue_id => '1', :id => '23'
125 125 end
126 126
127 127 context "issue reports" do
128 128 should_route :get, "/projects/567/issues/report", :controller => 'reports', :action => 'issue_report', :id => '567'
129 129 should_route :get, "/projects/567/issues/report/assigned_to", :controller => 'reports', :action => 'issue_report_details', :id => '567', :detail => 'assigned_to'
130 130 end
131 131
132 132 context "members" do
133 133 should_route :post, "/projects/5234/members/new", :controller => 'members', :action => 'new', :id => '5234'
134 134 end
135 135
136 136 context "messages" do
137 137 should_route :get, "/boards/22/topics/2", :controller => 'messages', :action => 'show', :id => '2', :board_id => '22'
138 138 should_route :get, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
139 139 should_route :get, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
140 140
141 141 should_route :post, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
142 142 should_route :post, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
143 143 should_route :post, "/boards/22/topics/555/replies", :controller => 'messages', :action => 'reply', :id => '555', :board_id => '22'
144 144 should_route :post, "/boards/22/topics/555/destroy", :controller => 'messages', :action => 'destroy', :id => '555', :board_id => '22'
145 145 end
146 146
147 147 context "news" do
148 148 should_route :get, "/news", :controller => 'news', :action => 'index'
149 149 should_route :get, "/news.atom", :controller => 'news', :action => 'index', :format => 'atom'
150 150 should_route :get, "/news.xml", :controller => 'news', :action => 'index', :format => 'xml'
151 151 should_route :get, "/news.json", :controller => 'news', :action => 'index', :format => 'json'
152 152 should_route :get, "/projects/567/news", :controller => 'news', :action => 'index', :project_id => '567'
153 153 should_route :get, "/projects/567/news.atom", :controller => 'news', :action => 'index', :format => 'atom', :project_id => '567'
154 154 should_route :get, "/projects/567/news.xml", :controller => 'news', :action => 'index', :format => 'xml', :project_id => '567'
155 155 should_route :get, "/projects/567/news.json", :controller => 'news', :action => 'index', :format => 'json', :project_id => '567'
156 156 should_route :get, "/news/2", :controller => 'news', :action => 'show', :id => '2'
157 157 should_route :get, "/projects/567/news/new", :controller => 'news', :action => 'new', :project_id => '567'
158 158 should_route :get, "/news/234", :controller => 'news', :action => 'show', :id => '234'
159 159 should_route :get, "/news/567/edit", :controller => 'news', :action => 'edit', :id => '567'
160 160 should_route :get, "/news/preview", :controller => 'previews', :action => 'news'
161 161
162 162 should_route :post, "/projects/567/news", :controller => 'news', :action => 'create', :project_id => '567'
163 163 should_route :post, "/news/567/comments", :controller => 'comments', :action => 'create', :id => '567'
164 164
165 165 should_route :put, "/news/567", :controller => 'news', :action => 'update', :id => '567'
166 166
167 167 should_route :delete, "/news/567", :controller => 'news', :action => 'destroy', :id => '567'
168 168 should_route :delete, "/news/567/comments/15", :controller => 'comments', :action => 'destroy', :id => '567', :comment_id => '15'
169 169 end
170 170
171 171 context "projects" do
172 172 should_route :get, "/projects", :controller => 'projects', :action => 'index'
173 173 should_route :get, "/projects.atom", :controller => 'projects', :action => 'index', :format => 'atom'
174 174 should_route :get, "/projects.xml", :controller => 'projects', :action => 'index', :format => 'xml'
175 175 should_route :get, "/projects/new", :controller => 'projects', :action => 'new'
176 176 should_route :get, "/projects/test", :controller => 'projects', :action => 'show', :id => 'test'
177 177 should_route :get, "/projects/1.xml", :controller => 'projects', :action => 'show', :id => '1', :format => 'xml'
178 178 should_route :get, "/projects/4223/settings", :controller => 'projects', :action => 'settings', :id => '4223'
179 179 should_route :get, "/projects/4223/settings/members", :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
180 180 should_route :get, "/projects/33/files", :controller => 'files', :action => 'index', :project_id => '33'
181 181 should_route :get, "/projects/33/files/new", :controller => 'files', :action => 'new', :project_id => '33'
182 182 should_route :get, "/projects/33/roadmap", :controller => 'versions', :action => 'index', :project_id => '33'
183 183 should_route :get, "/projects/33/activity", :controller => 'activities', :action => 'index', :id => '33'
184 184 should_route :get, "/projects/33/activity.atom", :controller => 'activities', :action => 'index', :id => '33', :format => 'atom'
185 185
186 186 should_route :post, "/projects", :controller => 'projects', :action => 'create'
187 187 should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml'
188 188 should_route :post, "/projects/33/files", :controller => 'files', :action => 'create', :project_id => '33'
189 189 should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64'
190 190 should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64'
191 191
192 192 should_route :put, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'update', :project_id => '64'
193 193 should_route :put, "/projects/4223", :controller => 'projects', :action => 'update', :id => '4223'
194 194 should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'update', :id => '1', :format => 'xml'
195 195
196 196 should_route :delete, "/projects/64", :controller => 'projects', :action => 'destroy', :id => '64'
197 197 should_route :delete, "/projects/1.xml", :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml'
198 198 should_route :delete, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'destroy', :project_id => '64'
199 199 end
200 200
201 201 context "repositories" do
202 202 should_route :get, "/projects/redmine/repository", :controller => 'repositories', :action => 'show', :id => 'redmine'
203 203 should_route :get, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
204 204 should_route :get, "/projects/redmine/repository/revisions", :controller => 'repositories', :action => 'revisions', :id => 'redmine'
205 205 should_route :get, "/projects/redmine/repository/revisions.atom", :controller => 'repositories', :action => 'revisions', :id => 'redmine', :format => 'atom'
206 206 should_route :get, "/projects/redmine/repository/revisions/2457", :controller => 'repositories', :action => 'revision', :id => 'redmine', :rev => '2457'
207 207 should_route :get, "/projects/redmine/repository/revisions/2457/diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457'
208 208 should_route :get, "/projects/redmine/repository/revisions/2457/diff.diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457', :format => 'diff'
209 209 should_route :get, "/projects/redmine/repository/diff/path/to/file.c", :controller => 'repositories', :action => 'diff', :id => 'redmine', :path => %w[path to file.c]
210 210 should_route :get, "/projects/redmine/repository/revisions/2/diff/path/to/file.c", :controller => 'repositories', :action => 'diff', :id => 'redmine', :path => %w[path to file.c], :rev => '2'
211 211 should_route :get, "/projects/redmine/repository/browse/path/to/file.c", :controller => 'repositories', :action => 'browse', :id => 'redmine', :path => %w[path to file.c]
212 212 should_route :get, "/projects/redmine/repository/entry/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c]
213 213 should_route :get, "/projects/redmine/repository/revisions/2/entry/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c], :rev => '2'
214 214 should_route :get, "/projects/redmine/repository/raw/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c], :format => 'raw'
215 215 should_route :get, "/projects/redmine/repository/revisions/2/raw/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c], :rev => '2', :format => 'raw'
216 216 should_route :get, "/projects/redmine/repository/annotate/path/to/file.c", :controller => 'repositories', :action => 'annotate', :id => 'redmine', :path => %w[path to file.c]
217 217 should_route :get, "/projects/redmine/repository/changes/path/to/file.c", :controller => 'repositories', :action => 'changes', :id => 'redmine', :path => %w[path to file.c]
218 218 should_route :get, "/projects/redmine/repository/statistics", :controller => 'repositories', :action => 'stats', :id => 'redmine'
219 219
220 220
221 221 should_route :post, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
222 222 end
223 223
224 224 context "timelogs (global)" do
225 225 should_route :get, "/time_entries", :controller => 'timelog', :action => 'index'
226 226 should_route :get, "/time_entries.csv", :controller => 'timelog', :action => 'index', :format => 'csv'
227 227 should_route :get, "/time_entries.atom", :controller => 'timelog', :action => 'index', :format => 'atom'
228 228 should_route :get, "/time_entries/new", :controller => 'timelog', :action => 'new'
229 229 should_route :get, "/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22'
230 230
231 231 should_route :post, "/time_entries", :controller => 'timelog', :action => 'create'
232 232
233 233 should_route :put, "/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22'
234 234
235 235 should_route :delete, "/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55'
236 236 end
237 237
238 238 context "timelogs (scoped under project)" do
239 239 should_route :get, "/projects/567/time_entries", :controller => 'timelog', :action => 'index', :project_id => '567'
240 240 should_route :get, "/projects/567/time_entries.csv", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'csv'
241 241 should_route :get, "/projects/567/time_entries.atom", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'atom'
242 242 should_route :get, "/projects/567/time_entries/new", :controller => 'timelog', :action => 'new', :project_id => '567'
243 243 should_route :get, "/projects/567/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :project_id => '567'
244 244
245 245 should_route :post, "/projects/567/time_entries", :controller => 'timelog', :action => 'create', :project_id => '567'
246 246
247 247 should_route :put, "/projects/567/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :project_id => '567'
248 248
249 249 should_route :delete, "/projects/567/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :project_id => '567'
250 250 end
251 251
252 252 context "timelogs (scoped under issues)" do
253 253 should_route :get, "/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234'
254 254 should_route :get, "/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'csv'
255 255 should_route :get, "/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'atom'
256 256 should_route :get, "/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234'
257 257 should_route :get, "/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234'
258 258
259 259 should_route :post, "/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234'
260 260
261 261 should_route :put, "/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234'
262 262
263 263 should_route :delete, "/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234'
264 264 end
265 265
266 266 context "timelogs (scoped under project and issues)" do
267 267 should_route :get, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook'
268 268 should_route :get, "/projects/ecookbook/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'csv'
269 269 should_route :get, "/projects/ecookbook/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'atom'
270 270 should_route :get, "/projects/ecookbook/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234', :project_id => 'ecookbook'
271 271 should_route :get, "/projects/ecookbook/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
272 272
273 273 should_route :post, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234', :project_id => 'ecookbook'
274 274
275 275 should_route :put, "/projects/ecookbook/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
276 276
277 277 should_route :delete, "/projects/ecookbook/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234', :project_id => 'ecookbook'
278 278 end
279 279
280 280 context "time_entry_reports" do
281 281 should_route :get, "/time_entries/report", :controller => 'time_entry_reports', :action => 'report'
282 282 should_route :get, "/projects/567/time_entries/report", :controller => 'time_entry_reports', :action => 'report', :project_id => '567'
283 283 should_route :get, "/projects/567/time_entries/report.csv", :controller => 'time_entry_reports', :action => 'report', :project_id => '567', :format => 'csv'
284 284 end
285 285
286 286 context "users" do
287 287 should_route :get, "/users", :controller => 'users', :action => 'index'
288 288 should_route :get, "/users/44", :controller => 'users', :action => 'show', :id => '44'
289 289 should_route :get, "/users/new", :controller => 'users', :action => 'new'
290 290 should_route :get, "/users/444/edit", :controller => 'users', :action => 'edit', :id => '444'
291 291 should_route :get, "/users/222/edit/membership", :controller => 'users', :action => 'edit', :id => '222', :tab => 'membership'
292 292
293 293 should_route :post, "/users", :controller => 'users', :action => 'create'
294 294 should_route :post, "/users/123/memberships", :controller => 'users', :action => 'edit_membership', :id => '123'
295 295 should_route :post, "/users/123/memberships/55", :controller => 'users', :action => 'edit_membership', :id => '123', :membership_id => '55'
296 296 should_route :post, "/users/567/memberships/12/destroy", :controller => 'users', :action => 'destroy_membership', :id => '567', :membership_id => '12'
297 297
298 298 should_route :put, "/users/444", :controller => 'users', :action => 'update', :id => '444'
299 299 end
300 300
301 301 # TODO: should they all be scoped under /projects/:project_id ?
302 302 context "versions" do
303 303 should_route :get, "/projects/foo/versions/new", :controller => 'versions', :action => 'new', :project_id => 'foo'
304 304 should_route :get, "/versions/show/1", :controller => 'versions', :action => 'show', :id => '1'
305 305 should_route :get, "/versions/edit/1", :controller => 'versions', :action => 'edit', :id => '1'
306 306
307 307 should_route :post, "/projects/foo/versions", :controller => 'versions', :action => 'create', :project_id => 'foo'
308 308 should_route :post, "/versions/update/1", :controller => 'versions', :action => 'update', :id => '1'
309 309
310 310 should_route :delete, "/versions/destroy/1", :controller => 'versions', :action => 'destroy', :id => '1'
311 311 end
312 312
313 313 context "wiki (singular, project's pages)" do
314 314 should_route :get, "/projects/567/wiki", :controller => 'wiki', :action => 'show', :project_id => '567'
315 315 should_route :get, "/projects/567/wiki/lalala", :controller => 'wiki', :action => 'show', :project_id => '567', :id => 'lalala'
316 316 should_route :get, "/projects/567/wiki/my_page/edit", :controller => 'wiki', :action => 'edit', :project_id => '567', :id => 'my_page'
317 317 should_route :get, "/projects/1/wiki/CookBook_documentation/history", :controller => 'wiki', :action => 'history', :project_id => '1', :id => 'CookBook_documentation'
318 318 should_route :get, "/projects/1/wiki/CookBook_documentation/diff/2/vs/1", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation', :version => '2', :version_from => '1'
319 319 should_route :get, "/projects/1/wiki/CookBook_documentation/annotate/2", :controller => 'wiki', :action => 'annotate', :project_id => '1', :id => 'CookBook_documentation', :version => '2'
320 320 should_route :get, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
321 321 should_route :get, "/projects/567/wiki/index", :controller => 'wiki', :action => 'index', :project_id => '567'
322 322 should_route :get, "/projects/567/wiki/date_index", :controller => 'wiki', :action => 'date_index', :project_id => '567'
323 323 should_route :get, "/projects/567/wiki/export", :controller => 'wiki', :action => 'export', :project_id => '567'
324 324
325 should_route :post, "/projects/567/wiki/my_page/edit", :controller => 'wiki', :action => 'update', :project_id => '567', :id => 'my_page'
326 325 should_route :post, "/projects/567/wiki/CookBook_documentation/preview", :controller => 'wiki', :action => 'preview', :project_id => '567', :id => 'CookBook_documentation'
327 326 should_route :post, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
328 327 should_route :post, "/projects/22/wiki/ladida/protect", :controller => 'wiki', :action => 'protect', :project_id => '22', :id => 'ladida'
329 328 should_route :post, "/projects/22/wiki/ladida/add_attachment", :controller => 'wiki', :action => 'add_attachment', :project_id => '22', :id => 'ladida'
330 329
330 should_route :put, "/projects/567/wiki/my_page", :controller => 'wiki', :action => 'update', :project_id => '567', :id => 'my_page'
331
331 332 should_route :delete, "/projects/22/wiki/ladida", :controller => 'wiki', :action => 'destroy', :project_id => '22', :id => 'ladida'
332 333 end
333 334
334 335 context "wikis (plural, admin setup)" do
335 336 should_route :get, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
336 337
337 338 should_route :post, "/projects/ladida/wiki", :controller => 'wikis', :action => 'edit', :id => 'ladida'
338 339 should_route :post, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
339 340 end
340 341
341 342 context "administration panel" do
342 343 should_route :get, "/admin/projects", :controller => 'admin', :action => 'projects'
343 344 end
344 345 end
@@ -1,631 +1,631
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../../test_helper'
19 19
20 20 class ApplicationHelperTest < ActionView::TestCase
21 21
22 22 fixtures :projects, :roles, :enabled_modules, :users,
23 23 :repositories, :changesets,
24 24 :trackers, :issue_statuses, :issues, :versions, :documents,
25 25 :wikis, :wiki_pages, :wiki_contents,
26 26 :boards, :messages,
27 27 :attachments,
28 28 :enumerations
29 29
30 30 def setup
31 31 super
32 32 end
33 33
34 34 context "#link_to_if_authorized" do
35 35 context "authorized user" do
36 36 should "be tested"
37 37 end
38 38
39 39 context "unauthorized user" do
40 40 should "be tested"
41 41 end
42 42
43 43 should "allow using the :controller and :action for the target link" do
44 44 User.current = User.find_by_login('admin')
45 45
46 46 @project = Issue.first.project # Used by helper
47 47 response = link_to_if_authorized("By controller/action",
48 48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
49 49 assert_match /href/, response
50 50 end
51 51
52 52 end
53 53
54 54 def test_auto_links
55 55 to_test = {
56 56 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
57 57 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
58 58 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
59 59 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
60 60 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
61 61 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
62 62 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
63 63 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
64 64 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
65 65 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
66 66 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
67 67 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
68 68 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
69 69 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
70 70 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
71 71 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
72 72 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
73 73 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
74 74 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
75 75 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
76 76 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
77 77 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
78 78 # two exclamation marks
79 79 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
80 80 # escaping
81 81 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
82 82 # wrap in angle brackets
83 83 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
84 84 }
85 85 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
86 86 end
87 87
88 88 def test_auto_mailto
89 89 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
90 90 textilizable('test@foo.bar')
91 91 end
92 92
93 93 def test_inline_images
94 94 to_test = {
95 95 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
96 96 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
97 97 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
98 98 # inline styles should be stripped
99 99 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
100 100 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
101 101 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
102 102 }
103 103 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
104 104 end
105 105
106 106 def test_inline_images_inside_tags
107 107 raw = <<-RAW
108 108 h1. !foo.png! Heading
109 109
110 110 Centered image:
111 111
112 112 p=. !bar.gif!
113 113 RAW
114 114
115 115 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
116 116 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
117 117 end
118 118
119 119 def test_acronyms
120 120 to_test = {
121 121 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
122 122 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
123 123 }
124 124 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
125 125
126 126 end
127 127
128 128 def test_attached_images
129 129 to_test = {
130 130 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
131 131 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
132 132 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
133 133 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
134 134 # link image
135 135 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3" title="This is a logo" alt="This is a logo" /></a>',
136 136 }
137 137 attachments = Attachment.find(:all)
138 138 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
139 139 end
140 140
141 141 def test_textile_external_links
142 142 to_test = {
143 143 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
144 144 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
145 145 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
146 146 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
147 147 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
148 148 # no multiline link text
149 149 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
150 150 # mailto link
151 151 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
152 152 # two exclamation marks
153 153 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
154 154 # escaping
155 155 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
156 156 }
157 157 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
158 158 end
159 159
160 160 def test_redmine_links
161 161 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
162 162 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
163 163
164 164 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
165 165 :class => 'changeset', :title => 'My very first commit')
166 166 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
167 167 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
168 168
169 169 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
170 170 :class => 'document')
171 171
172 172 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
173 173 :class => 'version')
174 174
175 175 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
176 176
177 177 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
178 178
179 179 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
180 180 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
181 181
182 182 to_test = {
183 183 # tickets
184 184 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
185 185 # changesets
186 186 'r1' => changeset_link,
187 187 'r1.' => "#{changeset_link}.",
188 188 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
189 189 'r1,r2' => "#{changeset_link},#{changeset_link2}",
190 190 # documents
191 191 'document#1' => document_link,
192 192 'document:"Test document"' => document_link,
193 193 # versions
194 194 'version#2' => version_link,
195 195 'version:1.0' => version_link,
196 196 'version:"1.0"' => version_link,
197 197 # source
198 198 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
199 199 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
200 200 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
201 201 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
202 202 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
203 203 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
204 204 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
205 205 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
206 206 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
207 207 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
208 208 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
209 209 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
210 210 # message
211 211 'message#4' => link_to('Post 2', message_url, :class => 'message'),
212 212 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
213 213 # project
214 214 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
215 215 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
216 216 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
217 217 # escaping
218 218 '!#3.' => '#3.',
219 219 '!r1' => 'r1',
220 220 '!document#1' => 'document#1',
221 221 '!document:"Test document"' => 'document:"Test document"',
222 222 '!version#2' => 'version#2',
223 223 '!version:1.0' => 'version:1.0',
224 224 '!version:"1.0"' => 'version:"1.0"',
225 225 '!source:/some/file' => 'source:/some/file',
226 226 # not found
227 227 '#0123456789' => '#0123456789',
228 228 # invalid expressions
229 229 'source:' => 'source:',
230 230 # url hash
231 231 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
232 232 }
233 233 @project = Project.find(1)
234 234 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
235 235 end
236 236
237 237 def test_attachment_links
238 238 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
239 239 to_test = {
240 240 'attachment:error281.txt' => attachment_link
241 241 }
242 242 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
243 243 end
244 244
245 245 def test_wiki_links
246 246 to_test = {
247 247 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
248 248 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
249 249 # link with anchor
250 250 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
251 251 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
252 252 # page that doesn't exist
253 253 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
254 254 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
255 255 # link to another project wiki
256 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
257 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
256 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
257 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
258 258 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
259 259 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
260 260 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
261 261 # striked through link
262 262 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
263 263 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
264 264 # escaping
265 265 '![[Another page|Page]]' => '[[Another page|Page]]',
266 266 # project does not exist
267 267 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
268 268 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
269 269 }
270 270 @project = Project.find(1)
271 271 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
272 272 end
273 273
274 274 def test_html_tags
275 275 to_test = {
276 276 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
277 277 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
278 278 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
279 279 # do not escape pre/code tags
280 280 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
281 281 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
282 282 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
283 283 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
284 284 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
285 285 # remove attributes except class
286 286 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
287 287 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
288 288 }
289 289 to_test.each { |text, result| assert_equal result, textilizable(text) }
290 290 end
291 291
292 292 def test_allowed_html_tags
293 293 to_test = {
294 294 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
295 295 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
296 296 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
297 297 }
298 298 to_test.each { |text, result| assert_equal result, textilizable(text) }
299 299 end
300 300
301 301 def test_pre_tags
302 302 raw = <<-RAW
303 303 Before
304 304
305 305 <pre>
306 306 <prepared-statement-cache-size>32</prepared-statement-cache-size>
307 307 </pre>
308 308
309 309 After
310 310 RAW
311 311
312 312 expected = <<-EXPECTED
313 313 <p>Before</p>
314 314 <pre>
315 315 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
316 316 </pre>
317 317 <p>After</p>
318 318 EXPECTED
319 319
320 320 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
321 321 end
322 322
323 323 def test_pre_content_should_not_parse_wiki_and_redmine_links
324 324 raw = <<-RAW
325 325 [[CookBook documentation]]
326 326
327 327 #1
328 328
329 329 <pre>
330 330 [[CookBook documentation]]
331 331
332 332 #1
333 333 </pre>
334 334 RAW
335 335
336 336 expected = <<-EXPECTED
337 337 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
338 338 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
339 339 <pre>
340 340 [[CookBook documentation]]
341 341
342 342 #1
343 343 </pre>
344 344 EXPECTED
345 345
346 346 @project = Project.find(1)
347 347 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
348 348 end
349 349
350 350 def test_non_closing_pre_blocks_should_be_closed
351 351 raw = <<-RAW
352 352 <pre><code>
353 353 RAW
354 354
355 355 expected = <<-EXPECTED
356 356 <pre><code>
357 357 </code></pre>
358 358 EXPECTED
359 359
360 360 @project = Project.find(1)
361 361 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
362 362 end
363 363
364 364 def test_syntax_highlight
365 365 raw = <<-RAW
366 366 <pre><code class="ruby">
367 367 # Some ruby code here
368 368 </code></pre>
369 369 RAW
370 370
371 371 expected = <<-EXPECTED
372 372 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
373 373 </code></pre>
374 374 EXPECTED
375 375
376 376 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
377 377 end
378 378
379 379 def test_wiki_links_in_tables
380 380 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
381 381 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
382 382 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
383 383 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
384 384 }
385 385 @project = Project.find(1)
386 386 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
387 387 end
388 388
389 389 def test_text_formatting
390 390 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
391 391 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
392 392 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
393 393 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
394 394 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
395 395 }
396 396 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
397 397 end
398 398
399 399 def test_wiki_horizontal_rule
400 400 assert_equal '<hr />', textilizable('---')
401 401 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
402 402 end
403 403
404 404 def test_acronym
405 405 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
406 406 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
407 407 end
408 408
409 409 def test_footnotes
410 410 raw = <<-RAW
411 411 This is some text[1].
412 412
413 413 fn1. This is the foot note
414 414 RAW
415 415
416 416 expected = <<-EXPECTED
417 417 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
418 418 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
419 419 EXPECTED
420 420
421 421 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
422 422 end
423 423
424 424 def test_table_of_content
425 425 raw = <<-RAW
426 426 {{toc}}
427 427
428 428 h1. Title
429 429
430 430 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
431 431
432 432 h2. Subtitle with a [[Wiki]] link
433 433
434 434 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
435 435
436 436 h2. Subtitle with [[Wiki|another Wiki]] link
437 437
438 438 h2. Subtitle with %{color:red}red text%
439 439
440 440 h1. Another title
441 441
442 442 h2. An "Internet link":http://www.redmine.org/ inside subtitle
443 443
444 444 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
445 445
446 446 RAW
447 447
448 448 expected = '<ul class="toc">' +
449 449 '<li class="heading1"><a href="#Title">Title</a></li>' +
450 450 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
451 451 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
452 452 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
453 453 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
454 454 '<li class="heading2"><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
455 455 '<li class="heading2"><a href="#Project-Name">Project Name</a></li>' +
456 456 '</ul>'
457 457
458 458 assert textilizable(raw).gsub("\n", "").include?(expected)
459 459 end
460 460
461 461 def test_blockquote
462 462 # orig raw text
463 463 raw = <<-RAW
464 464 John said:
465 465 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
466 466 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
467 467 > * Donec odio lorem,
468 468 > * sagittis ac,
469 469 > * malesuada in,
470 470 > * adipiscing eu, dolor.
471 471 >
472 472 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
473 473 > Proin a tellus. Nam vel neque.
474 474
475 475 He's right.
476 476 RAW
477 477
478 478 # expected html
479 479 expected = <<-EXPECTED
480 480 <p>John said:</p>
481 481 <blockquote>
482 482 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
483 483 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
484 484 <ul>
485 485 <li>Donec odio lorem,</li>
486 486 <li>sagittis ac,</li>
487 487 <li>malesuada in,</li>
488 488 <li>adipiscing eu, dolor.</li>
489 489 </ul>
490 490 <blockquote>
491 491 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
492 492 </blockquote>
493 493 <p>Proin a tellus. Nam vel neque.</p>
494 494 </blockquote>
495 495 <p>He's right.</p>
496 496 EXPECTED
497 497
498 498 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
499 499 end
500 500
501 501 def test_table
502 502 raw = <<-RAW
503 503 This is a table with empty cells:
504 504
505 505 |cell11|cell12||
506 506 |cell21||cell23|
507 507 |cell31|cell32|cell33|
508 508 RAW
509 509
510 510 expected = <<-EXPECTED
511 511 <p>This is a table with empty cells:</p>
512 512
513 513 <table>
514 514 <tr><td>cell11</td><td>cell12</td><td></td></tr>
515 515 <tr><td>cell21</td><td></td><td>cell23</td></tr>
516 516 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
517 517 </table>
518 518 EXPECTED
519 519
520 520 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
521 521 end
522 522
523 523 def test_table_with_line_breaks
524 524 raw = <<-RAW
525 525 This is a table with line breaks:
526 526
527 527 |cell11
528 528 continued|cell12||
529 529 |-cell21-||cell23
530 530 cell23 line2
531 531 cell23 *line3*|
532 532 |cell31|cell32
533 533 cell32 line2|cell33|
534 534
535 535 RAW
536 536
537 537 expected = <<-EXPECTED
538 538 <p>This is a table with line breaks:</p>
539 539
540 540 <table>
541 541 <tr>
542 542 <td>cell11<br />continued</td>
543 543 <td>cell12</td>
544 544 <td></td>
545 545 </tr>
546 546 <tr>
547 547 <td><del>cell21</del></td>
548 548 <td></td>
549 549 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
550 550 </tr>
551 551 <tr>
552 552 <td>cell31</td>
553 553 <td>cell32<br/>cell32 line2</td>
554 554 <td>cell33</td>
555 555 </tr>
556 556 </table>
557 557 EXPECTED
558 558
559 559 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
560 560 end
561 561
562 562 def test_textile_should_not_mangle_brackets
563 563 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
564 564 end
565 565
566 566 def test_default_formatter
567 567 Setting.text_formatting = 'unknown'
568 568 text = 'a *link*: http://www.example.net/'
569 569 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
570 570 Setting.text_formatting = 'textile'
571 571 end
572 572
573 573 def test_due_date_distance_in_words
574 574 to_test = { Date.today => 'Due in 0 days',
575 575 Date.today + 1 => 'Due in 1 day',
576 576 Date.today + 100 => 'Due in about 3 months',
577 577 Date.today + 20000 => 'Due in over 54 years',
578 578 Date.today - 1 => '1 day late',
579 579 Date.today - 100 => 'about 3 months late',
580 580 Date.today - 20000 => 'over 54 years late',
581 581 }
582 582 to_test.each do |date, expected|
583 583 assert_equal expected, due_date_distance_in_words(date)
584 584 end
585 585 end
586 586
587 587 def test_avatar
588 588 # turn on avatars
589 589 Setting.gravatar_enabled = '1'
590 590 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
591 591 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
592 592 assert_nil avatar('jsmith')
593 593 assert_nil avatar(nil)
594 594
595 595 # turn off avatars
596 596 Setting.gravatar_enabled = '0'
597 597 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
598 598 end
599 599
600 600 def test_link_to_user
601 601 user = User.find(2)
602 602 t = link_to_user(user)
603 603 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
604 604 end
605 605
606 606 def test_link_to_user_should_not_link_to_locked_user
607 607 user = User.find(5)
608 608 assert user.locked?
609 609 t = link_to_user(user)
610 610 assert_equal user.name, t
611 611 end
612 612
613 613 def test_link_to_user_should_not_link_to_anonymous
614 614 user = User.anonymous
615 615 assert user.anonymous?
616 616 t = link_to_user(user)
617 617 assert_equal ::I18n.t(:label_user_anonymous), t
618 618 end
619 619
620 620 def test_link_to_project
621 621 project = Project.find(1)
622 622 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
623 623 link_to_project(project)
624 624 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
625 625 link_to_project(project, :action => 'settings')
626 626 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
627 627 link_to_project(project, {:only_path => false, :jump => 'blah'})
628 628 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
629 629 link_to_project(project, {:action => 'settings'}, :class => "project")
630 630 end
631 631 end
General Comments 0
You need to be logged in to leave comments. Login now