##// END OF EJS Templates
Adds export of all wiki pages to a PDF file (#3463)....
Jean-Philippe Lang -
r8614:3d27bf5318ef
parent child
Show More
@@ -1,317 +1,325
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'diff'
18 require 'diff'
19
19
20 # The WikiController follows the Rails REST controller pattern but with
20 # The WikiController follows the Rails REST controller pattern but with
21 # a few differences
21 # a few differences
22 #
22 #
23 # * index - shows a list of WikiPages grouped by page or date
23 # * index - shows a list of WikiPages grouped by page or date
24 # * new - not used
24 # * new - not used
25 # * create - not used
25 # * create - not used
26 # * show - will also show the form for creating a new wiki page
26 # * show - will also show the form for creating a new wiki page
27 # * edit - used to edit an existing or new page
27 # * edit - used to edit an existing or new page
28 # * update - used to save a wiki page update to the database, including new pages
28 # * update - used to save a wiki page update to the database, including new pages
29 # * destroy - normal
29 # * destroy - normal
30 #
30 #
31 # Other member and collection methods are also used
31 # Other member and collection methods are also used
32 #
32 #
33 # TODO: still being worked on
33 # TODO: still being worked on
34 class WikiController < ApplicationController
34 class WikiController < ApplicationController
35 default_search_scope :wiki_pages
35 default_search_scope :wiki_pages
36 before_filter :find_wiki, :authorize
36 before_filter :find_wiki, :authorize
37 before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
37 before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
38 before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
38 before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
39
39
40 helper :attachments
40 helper :attachments
41 include AttachmentsHelper
41 include AttachmentsHelper
42 helper :watchers
42 helper :watchers
43 include Redmine::Export::PDF
43 include Redmine::Export::PDF
44
44
45 # List of pages, sorted alphabetically and by parent (hierarchy)
45 # List of pages, sorted alphabetically and by parent (hierarchy)
46 def index
46 def index
47 load_pages_for_index
47 load_pages_for_index
48 @pages_by_parent_id = @pages.group_by(&:parent_id)
48 @pages_by_parent_id = @pages.group_by(&:parent_id)
49 end
49 end
50
50
51 # List of page, by last update
51 # List of page, by last update
52 def date_index
52 def date_index
53 load_pages_for_index
53 load_pages_for_index
54 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
54 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
55 end
55 end
56
56
57 # display a page (in editing mode if it doesn't exist)
57 # display a page (in editing mode if it doesn't exist)
58 def show
58 def show
59 if @page.new_record?
59 if @page.new_record?
60 if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
60 if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
61 edit
61 edit
62 render :action => 'edit'
62 render :action => 'edit'
63 else
63 else
64 render_404
64 render_404
65 end
65 end
66 return
66 return
67 end
67 end
68 if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
68 if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
69 # Redirects user to the current version if he's not allowed to view previous versions
69 # Redirects user to the current version if he's not allowed to view previous versions
70 redirect_to :version => nil
70 redirect_to :version => nil
71 return
71 return
72 end
72 end
73 @content = @page.content_for_version(params[:version])
73 @content = @page.content_for_version(params[:version])
74 if User.current.allowed_to?(:export_wiki_pages, @project)
74 if User.current.allowed_to?(:export_wiki_pages, @project)
75 if params[:format] == 'pdf'
75 if params[:format] == 'pdf'
76 send_data(wiki_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
76 send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
77 return
77 return
78 elsif params[:format] == 'html'
78 elsif params[:format] == 'html'
79 export = render_to_string :action => 'export', :layout => false
79 export = render_to_string :action => 'export', :layout => false
80 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
80 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
81 return
81 return
82 elsif params[:format] == 'txt'
82 elsif params[:format] == 'txt'
83 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
83 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
84 return
84 return
85 end
85 end
86 end
86 end
87 @editable = editable?
87 @editable = editable?
88 @sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) &&
88 @sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) &&
89 @content.current_version? &&
89 @content.current_version? &&
90 Redmine::WikiFormatting.supports_section_edit?
90 Redmine::WikiFormatting.supports_section_edit?
91
91
92 render :action => 'show'
92 render :action => 'show'
93 end
93 end
94
94
95 # edit an existing page or a new one
95 # edit an existing page or a new one
96 def edit
96 def edit
97 return render_403 unless editable?
97 return render_403 unless editable?
98 if @page.new_record?
98 if @page.new_record?
99 @page.content = WikiContent.new(:page => @page)
99 @page.content = WikiContent.new(:page => @page)
100 if params[:parent].present?
100 if params[:parent].present?
101 @page.parent = @page.wiki.find_page(params[:parent].to_s)
101 @page.parent = @page.wiki.find_page(params[:parent].to_s)
102 end
102 end
103 end
103 end
104
104
105 @content = @page.content_for_version(params[:version])
105 @content = @page.content_for_version(params[:version])
106 @content.text = initial_page_content(@page) if @content.text.blank?
106 @content.text = initial_page_content(@page) if @content.text.blank?
107 # don't keep previous comment
107 # don't keep previous comment
108 @content.comments = nil
108 @content.comments = nil
109
109
110 # To prevent StaleObjectError exception when reverting to a previous version
110 # To prevent StaleObjectError exception when reverting to a previous version
111 @content.version = @page.content.version
111 @content.version = @page.content.version
112
112
113 @text = @content.text
113 @text = @content.text
114 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
114 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
115 @section = params[:section].to_i
115 @section = params[:section].to_i
116 @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section)
116 @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section)
117 render_404 if @text.blank?
117 render_404 if @text.blank?
118 end
118 end
119 end
119 end
120
120
121 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
121 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
122 # Creates a new page or updates an existing one
122 # Creates a new page or updates an existing one
123 def update
123 def update
124 return render_403 unless editable?
124 return render_403 unless editable?
125 @page.content = WikiContent.new(:page => @page) if @page.new_record?
125 @page.content = WikiContent.new(:page => @page) if @page.new_record?
126
126
127 @content = @page.content_for_version(params[:version])
127 @content = @page.content_for_version(params[:version])
128 @content.text = initial_page_content(@page) if @content.text.blank?
128 @content.text = initial_page_content(@page) if @content.text.blank?
129 # don't keep previous comment
129 # don't keep previous comment
130 @content.comments = nil
130 @content.comments = nil
131
131
132 if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text]
132 if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text]
133 attachments = Attachment.attach_files(@page, params[:attachments])
133 attachments = Attachment.attach_files(@page, params[:attachments])
134 render_attachment_warning_if_needed(@page)
134 render_attachment_warning_if_needed(@page)
135 # don't save if text wasn't changed
135 # don't save if text wasn't changed
136 redirect_to :action => 'show', :project_id => @project, :id => @page.title
136 redirect_to :action => 'show', :project_id => @project, :id => @page.title
137 return
137 return
138 end
138 end
139
139
140 @content.comments = params[:content][:comments]
140 @content.comments = params[:content][:comments]
141 @text = params[:content][:text]
141 @text = params[:content][:text]
142 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
142 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
143 @section = params[:section].to_i
143 @section = params[:section].to_i
144 @section_hash = params[:section_hash]
144 @section_hash = params[:section_hash]
145 @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
145 @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
146 else
146 else
147 @content.version = params[:content][:version]
147 @content.version = params[:content][:version]
148 @content.text = @text
148 @content.text = @text
149 end
149 end
150 @content.author = User.current
150 @content.author = User.current
151 if @page.new_record? && params[:page]
151 if @page.new_record? && params[:page]
152 @page.parent_id = params[:page][:parent_id]
152 @page.parent_id = params[:page][:parent_id]
153 end
153 end
154 # if page is new @page.save will also save content, but not if page isn't a new record
154 # if page is new @page.save will also save content, but not if page isn't a new record
155 if (@page.new_record? ? @page.save : @content.save)
155 if (@page.new_record? ? @page.save : @content.save)
156 attachments = Attachment.attach_files(@page, params[:attachments])
156 attachments = Attachment.attach_files(@page, params[:attachments])
157 render_attachment_warning_if_needed(@page)
157 render_attachment_warning_if_needed(@page)
158 call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
158 call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
159 redirect_to :action => 'show', :project_id => @project, :id => @page.title
159 redirect_to :action => 'show', :project_id => @project, :id => @page.title
160 else
160 else
161 render :action => 'edit'
161 render :action => 'edit'
162 end
162 end
163
163
164 rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
164 rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
165 # Optimistic locking exception
165 # Optimistic locking exception
166 flash.now[:error] = l(:notice_locking_conflict)
166 flash.now[:error] = l(:notice_locking_conflict)
167 render :action => 'edit'
167 render :action => 'edit'
168 end
168 end
169
169
170 # rename a page
170 # rename a page
171 def rename
171 def rename
172 return render_403 unless editable?
172 return render_403 unless editable?
173 @page.redirect_existing_links = true
173 @page.redirect_existing_links = true
174 # used to display the *original* title if some AR validation errors occur
174 # used to display the *original* title if some AR validation errors occur
175 @original_title = @page.pretty_title
175 @original_title = @page.pretty_title
176 if request.post? && @page.update_attributes(params[:wiki_page])
176 if request.post? && @page.update_attributes(params[:wiki_page])
177 flash[:notice] = l(:notice_successful_update)
177 flash[:notice] = l(:notice_successful_update)
178 redirect_to :action => 'show', :project_id => @project, :id => @page.title
178 redirect_to :action => 'show', :project_id => @project, :id => @page.title
179 end
179 end
180 end
180 end
181
181
182 verify :method => :post, :only => :protect, :redirect_to => { :action => :show }
182 verify :method => :post, :only => :protect, :redirect_to => { :action => :show }
183 def protect
183 def protect
184 @page.update_attribute :protected, params[:protected]
184 @page.update_attribute :protected, params[:protected]
185 redirect_to :action => 'show', :project_id => @project, :id => @page.title
185 redirect_to :action => 'show', :project_id => @project, :id => @page.title
186 end
186 end
187
187
188 # show page history
188 # show page history
189 def history
189 def history
190 @version_count = @page.content.versions.count
190 @version_count = @page.content.versions.count
191 @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
191 @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
192 # don't load text
192 # don't load text
193 @versions = @page.content.versions.find :all,
193 @versions = @page.content.versions.find :all,
194 :select => "id, author_id, comments, updated_on, version",
194 :select => "id, author_id, comments, updated_on, version",
195 :order => 'version DESC',
195 :order => 'version DESC',
196 :limit => @version_pages.items_per_page + 1,
196 :limit => @version_pages.items_per_page + 1,
197 :offset => @version_pages.current.offset
197 :offset => @version_pages.current.offset
198
198
199 render :layout => false if request.xhr?
199 render :layout => false if request.xhr?
200 end
200 end
201
201
202 def diff
202 def diff
203 @diff = @page.diff(params[:version], params[:version_from])
203 @diff = @page.diff(params[:version], params[:version_from])
204 render_404 unless @diff
204 render_404 unless @diff
205 end
205 end
206
206
207 def annotate
207 def annotate
208 @annotate = @page.annotate(params[:version])
208 @annotate = @page.annotate(params[:version])
209 render_404 unless @annotate
209 render_404 unless @annotate
210 end
210 end
211
211
212 verify :method => :delete, :only => [:destroy], :redirect_to => { :action => :show }
212 verify :method => :delete, :only => [:destroy], :redirect_to => { :action => :show }
213 # Removes a wiki page and its history
213 # Removes a wiki page and its history
214 # Children can be either set as root pages, removed or reassigned to another parent page
214 # Children can be either set as root pages, removed or reassigned to another parent page
215 def destroy
215 def destroy
216 return render_403 unless editable?
216 return render_403 unless editable?
217
217
218 @descendants_count = @page.descendants.size
218 @descendants_count = @page.descendants.size
219 if @descendants_count > 0
219 if @descendants_count > 0
220 case params[:todo]
220 case params[:todo]
221 when 'nullify'
221 when 'nullify'
222 # Nothing to do
222 # Nothing to do
223 when 'destroy'
223 when 'destroy'
224 # Removes all its descendants
224 # Removes all its descendants
225 @page.descendants.each(&:destroy)
225 @page.descendants.each(&:destroy)
226 when 'reassign'
226 when 'reassign'
227 # Reassign children to another parent page
227 # Reassign children to another parent page
228 reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
228 reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
229 return unless reassign_to
229 return unless reassign_to
230 @page.children.each do |child|
230 @page.children.each do |child|
231 child.update_attribute(:parent, reassign_to)
231 child.update_attribute(:parent, reassign_to)
232 end
232 end
233 else
233 else
234 @reassignable_to = @wiki.pages - @page.self_and_descendants
234 @reassignable_to = @wiki.pages - @page.self_and_descendants
235 return
235 return
236 end
236 end
237 end
237 end
238 @page.destroy
238 @page.destroy
239 redirect_to :action => 'index', :project_id => @project
239 redirect_to :action => 'index', :project_id => @project
240 end
240 end
241
241
242 # Export wiki to a single html file
242 # Export wiki to a single pdf or html file
243 def export
243 def export
244 if User.current.allowed_to?(:export_wiki_pages, @project)
244 unless User.current.allowed_to?(:export_wiki_pages, @project)
245 @pages = @wiki.pages.find :all, :order => 'title'
245 redirect_to :action => 'show', :project_id => @project, :id => nil
246 return
247 end
248
249 @pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments], :limit => 75)
250 respond_to do |format|
251 format.html {
246 export = render_to_string :action => 'export_multiple', :layout => false
252 export = render_to_string :action => 'export_multiple', :layout => false
247 send_data(export, :type => 'text/html', :filename => "wiki.html")
253 send_data(export, :type => 'text/html', :filename => "wiki.html")
248 else
254 }
249 redirect_to :action => 'show', :project_id => @project, :id => nil
255 format.pdf {
256 send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
257 }
250 end
258 end
251 end
259 end
252
260
253 def preview
261 def preview
254 page = @wiki.find_page(params[:id])
262 page = @wiki.find_page(params[:id])
255 # page is nil when previewing a new page
263 # page is nil when previewing a new page
256 return render_403 unless page.nil? || editable?(page)
264 return render_403 unless page.nil? || editable?(page)
257 if page
265 if page
258 @attachements = page.attachments
266 @attachements = page.attachments
259 @previewed = page.content
267 @previewed = page.content
260 end
268 end
261 @text = params[:content][:text]
269 @text = params[:content][:text]
262 render :partial => 'common/preview'
270 render :partial => 'common/preview'
263 end
271 end
264
272
265 def add_attachment
273 def add_attachment
266 return render_403 unless editable?
274 return render_403 unless editable?
267 attachments = Attachment.attach_files(@page, params[:attachments])
275 attachments = Attachment.attach_files(@page, params[:attachments])
268 render_attachment_warning_if_needed(@page)
276 render_attachment_warning_if_needed(@page)
269 redirect_to :action => 'show', :id => @page.title, :project_id => @project
277 redirect_to :action => 'show', :id => @page.title, :project_id => @project
270 end
278 end
271
279
272 private
280 private
273
281
274 def find_wiki
282 def find_wiki
275 @project = Project.find(params[:project_id])
283 @project = Project.find(params[:project_id])
276 @wiki = @project.wiki
284 @wiki = @project.wiki
277 render_404 unless @wiki
285 render_404 unless @wiki
278 rescue ActiveRecord::RecordNotFound
286 rescue ActiveRecord::RecordNotFound
279 render_404
287 render_404
280 end
288 end
281
289
282 # Finds the requested page or a new page if it doesn't exist
290 # Finds the requested page or a new page if it doesn't exist
283 def find_existing_or_new_page
291 def find_existing_or_new_page
284 @page = @wiki.find_or_new_page(params[:id])
292 @page = @wiki.find_or_new_page(params[:id])
285 if @wiki.page_found_with_redirect?
293 if @wiki.page_found_with_redirect?
286 redirect_to params.update(:id => @page.title)
294 redirect_to params.update(:id => @page.title)
287 end
295 end
288 end
296 end
289
297
290 # Finds the requested page and returns a 404 error if it doesn't exist
298 # Finds the requested page and returns a 404 error if it doesn't exist
291 def find_existing_page
299 def find_existing_page
292 @page = @wiki.find_page(params[:id])
300 @page = @wiki.find_page(params[:id])
293 if @page.nil?
301 if @page.nil?
294 render_404
302 render_404
295 return
303 return
296 end
304 end
297 if @wiki.page_found_with_redirect?
305 if @wiki.page_found_with_redirect?
298 redirect_to params.update(:id => @page.title)
306 redirect_to params.update(:id => @page.title)
299 end
307 end
300 end
308 end
301
309
302 # Returns true if the current user is allowed to edit the page, otherwise false
310 # Returns true if the current user is allowed to edit the page, otherwise false
303 def editable?(page = @page)
311 def editable?(page = @page)
304 page.editable_by?(User.current)
312 page.editable_by?(User.current)
305 end
313 end
306
314
307 # Returns the default content of a new wiki page
315 # Returns the default content of a new wiki page
308 def initial_page_content(page)
316 def initial_page_content(page)
309 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
317 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
310 extend helper unless self.instance_of?(helper)
318 extend helper unless self.instance_of?(helper)
311 helper.instance_method(:initial_page_content).bind(self).call(page)
319 helper.instance_method(:initial_page_content).bind(self).call(page)
312 end
320 end
313
321
314 def load_pages_for_index
322 def load_pages_for_index
315 @pages = @wiki.pages.with_updated_on.all(:order => 'title', :include => {:wiki => :project})
323 @pages = @wiki.pages.with_updated_on.all(:order => 'title', :include => {:wiki => :project})
316 end
324 end
317 end
325 end
@@ -1,33 +1,36
1 <div class="contextual">
1 <div class="contextual">
2 <%= watcher_tag(@wiki, User.current) %>
2 <%= watcher_tag(@wiki, User.current) %>
3 </div>
3 </div>
4
4
5 <h2><%= l(:label_index_by_date) %></h2>
5 <h2><%= l(:label_index_by_date) %></h2>
6
6
7 <% if @pages.empty? %>
7 <% if @pages.empty? %>
8 <p class="nodata"><%= l(:label_no_data) %></p>
8 <p class="nodata"><%= l(:label_no_data) %></p>
9 <% end %>
9 <% end %>
10
10
11 <% @pages_by_date.keys.sort.reverse.each do |date| %>
11 <% @pages_by_date.keys.sort.reverse.each do |date| %>
12 <h3><%= format_date(date) %></h3>
12 <h3><%= format_date(date) %></h3>
13 <ul>
13 <ul>
14 <% @pages_by_date[date].each do |page| %>
14 <% @pages_by_date[date].each do |page| %>
15 <li><%= link_to h(page.pretty_title), :action => 'show', :id => page.title, :project_id => page.project %></li>
15 <li><%= link_to h(page.pretty_title), :action => 'show', :id => page.title, :project_id => page.project %></li>
16 <% end %>
16 <% end %>
17 </ul>
17 </ul>
18 <% end %>
18 <% end %>
19
19
20 <% content_for :sidebar do %>
20 <% content_for :sidebar do %>
21 <%= render :partial => 'sidebar' %>
21 <%= render :partial => 'sidebar' %>
22 <% end %>
22 <% end %>
23
23
24 <% unless @pages.empty? %>
24 <% unless @pages.empty? %>
25 <% other_formats_links do |f| %>
25 <% other_formats_links do |f| %>
26 <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %>
26 <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %>
27 <%= f.link_to('HTML', :url => {:action => 'export'}) if User.current.allowed_to?(:export_wiki_pages, @project) %>
27 <% if User.current.allowed_to?(:export_wiki_pages, @project) %>
28 <%= f.link_to('PDF', :url => {:action => 'export', :format => 'pdf'}) %>
29 <%= f.link_to('HTML', :url => {:action => 'export'}) %>
30 <% end %>
28 <% end %>
31 <% end %>
29 <% end %>
32 <% end %>
30
33
31 <% content_for :header_tags do %>
34 <% content_for :header_tags do %>
32 <%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %>
35 <%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %>
33 <% end %>
36 <% end %>
@@ -1,34 +1,35
1 <div class="contextual">
1 <div class="contextual">
2 <%= watcher_tag(@wiki, User.current) %>
2 <%= watcher_tag(@wiki, User.current) %>
3 </div>
3 </div>
4
4
5 <h2><%= l(:label_index_by_title) %></h2>
5 <h2><%= l(:label_index_by_title) %></h2>
6
6
7 <% if @pages.empty? %>
7 <% if @pages.empty? %>
8 <p class="nodata"><%= l(:label_no_data) %></p>
8 <p class="nodata"><%= l(:label_no_data) %></p>
9 <% end %>
9 <% end %>
10
10
11 <%= render_page_hierarchy(@pages_by_parent_id, nil, :timestamp => true) %>
11 <%= render_page_hierarchy(@pages_by_parent_id, nil, :timestamp => true) %>
12
12
13 <% content_for :sidebar do %>
13 <% content_for :sidebar do %>
14 <%= render :partial => 'sidebar' %>
14 <%= render :partial => 'sidebar' %>
15 <% end %>
15 <% end %>
16
16
17 <% unless @pages.empty? %>
17 <% unless @pages.empty? %>
18 <% other_formats_links do |f| %>
18 <% other_formats_links do |f| %>
19 <%= f.link_to 'Atom',
19 <%= f.link_to 'Atom',
20 :url => {:controller => 'activities', :action => 'index',
20 :url => {:controller => 'activities', :action => 'index',
21 :id => @project, :show_wiki_edits => 1,
21 :id => @project, :show_wiki_edits => 1,
22 :key => User.current.rss_key} %>
22 :key => User.current.rss_key} %>
23 <%= f.link_to('HTML',
23 <% if User.current.allowed_to?(:export_wiki_pages, @project) %>
24 :url => {:action => 'export'}
24 <%= f.link_to('PDF', :url => {:action => 'export', :format => 'pdf'}) %>
25 ) if User.current.allowed_to?(:export_wiki_pages, @project) %>
25 <%= f.link_to('HTML', :url => {:action => 'export'}) %>
26 <% end %>
26 <% end %>
27 <% end %>
27 <% end %>
28 <% end %>
28
29
29 <% content_for :header_tags do %>
30 <% content_for :header_tags do %>
30 <%= auto_discovery_link_tag(
31 <%= auto_discovery_link_tag(
31 :atom, :controller => 'activities', :action => 'index',
32 :atom, :controller => 'activities', :action => 'index',
32 :id => @project, :show_wiki_edits => 1, :format => 'atom',
33 :id => @project, :show_wiki_edits => 1, :format => 'atom',
33 :key => User.current.rss_key) %>
34 :key => User.current.rss_key) %>
34 <% end %>
35 <% end %>
@@ -1,634 +1,670
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'iconv'
20 require 'iconv'
21 require 'fpdf/chinese'
21 require 'fpdf/chinese'
22 require 'fpdf/japanese'
22 require 'fpdf/japanese'
23 require 'fpdf/korean'
23 require 'fpdf/korean'
24 require 'core/rmagick'
24 require 'core/rmagick'
25
25
26 module Redmine
26 module Redmine
27 module Export
27 module Export
28 module PDF
28 module PDF
29 include ActionView::Helpers::TextHelper
29 include ActionView::Helpers::TextHelper
30 include ActionView::Helpers::NumberHelper
30 include ActionView::Helpers::NumberHelper
31 include IssuesHelper
31 include IssuesHelper
32
32
33 class ITCPDF < TCPDF
33 class ITCPDF < TCPDF
34 include Redmine::I18n
34 include Redmine::I18n
35 attr_accessor :footer_date
35 attr_accessor :footer_date
36
36
37 def initialize(lang)
37 def initialize(lang)
38 @@k_path_cache = Rails.root.join('tmp', 'pdf')
38 @@k_path_cache = Rails.root.join('tmp', 'pdf')
39 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache)
39 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache)
40 set_language_if_valid lang
40 set_language_if_valid lang
41 pdf_encoding = l(:general_pdf_encoding).upcase
41 pdf_encoding = l(:general_pdf_encoding).upcase
42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
43 case current_language.to_s.downcase
43 case current_language.to_s.downcase
44 when 'vi'
44 when 'vi'
45 @font_for_content = 'DejaVuSans'
45 @font_for_content = 'DejaVuSans'
46 @font_for_footer = 'DejaVuSans'
46 @font_for_footer = 'DejaVuSans'
47 else
47 else
48 case pdf_encoding
48 case pdf_encoding
49 when 'UTF-8'
49 when 'UTF-8'
50 @font_for_content = 'FreeSans'
50 @font_for_content = 'FreeSans'
51 @font_for_footer = 'FreeSans'
51 @font_for_footer = 'FreeSans'
52 when 'CP949'
52 when 'CP949'
53 extend(PDF_Korean)
53 extend(PDF_Korean)
54 AddUHCFont()
54 AddUHCFont()
55 @font_for_content = 'UHC'
55 @font_for_content = 'UHC'
56 @font_for_footer = 'UHC'
56 @font_for_footer = 'UHC'
57 when 'CP932', 'SJIS', 'SHIFT_JIS'
57 when 'CP932', 'SJIS', 'SHIFT_JIS'
58 extend(PDF_Japanese)
58 extend(PDF_Japanese)
59 AddSJISFont()
59 AddSJISFont()
60 @font_for_content = 'SJIS'
60 @font_for_content = 'SJIS'
61 @font_for_footer = 'SJIS'
61 @font_for_footer = 'SJIS'
62 when 'GB18030'
62 when 'GB18030'
63 extend(PDF_Chinese)
63 extend(PDF_Chinese)
64 AddGBFont()
64 AddGBFont()
65 @font_for_content = 'GB'
65 @font_for_content = 'GB'
66 @font_for_footer = 'GB'
66 @font_for_footer = 'GB'
67 when 'BIG5'
67 when 'BIG5'
68 extend(PDF_Chinese)
68 extend(PDF_Chinese)
69 AddBig5Font()
69 AddBig5Font()
70 @font_for_content = 'Big5'
70 @font_for_content = 'Big5'
71 @font_for_footer = 'Big5'
71 @font_for_footer = 'Big5'
72 else
72 else
73 @font_for_content = 'Arial'
73 @font_for_content = 'Arial'
74 @font_for_footer = 'Helvetica'
74 @font_for_footer = 'Helvetica'
75 end
75 end
76 end
76 end
77 SetCreator(Redmine::Info.app_name)
77 SetCreator(Redmine::Info.app_name)
78 SetFont(@font_for_content)
78 SetFont(@font_for_content)
79 @outlines = []
79 @outlines = []
80 @outlineRoot = nil
80 @outlineRoot = nil
81 end
81 end
82
82
83 def SetFontStyle(style, size)
83 def SetFontStyle(style, size)
84 SetFont(@font_for_content, style, size)
84 SetFont(@font_for_content, style, size)
85 end
85 end
86
86
87 def SetTitle(txt)
87 def SetTitle(txt)
88 txt = begin
88 txt = begin
89 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
89 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
90 hextxt = "<FEFF" # FEFF is BOM
90 hextxt = "<FEFF" # FEFF is BOM
91 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
91 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
92 hextxt << ">"
92 hextxt << ">"
93 rescue
93 rescue
94 txt
94 txt
95 end || ''
95 end || ''
96 super(txt)
96 super(txt)
97 end
97 end
98
98
99 def textstring(s)
99 def textstring(s)
100 # Format a text string
100 # Format a text string
101 if s =~ /^</ # This means the string is hex-dumped.
101 if s =~ /^</ # This means the string is hex-dumped.
102 return s
102 return s
103 else
103 else
104 return '('+escape(s)+')'
104 return '('+escape(s)+')'
105 end
105 end
106 end
106 end
107
107
108 def fix_text_encoding(txt)
108 def fix_text_encoding(txt)
109 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
109 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
110 end
110 end
111
111
112 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
112 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
113 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
113 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
114 end
114 end
115
115
116 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
116 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
117 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
117 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
118 end
118 end
119
119
120 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
120 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
121 @attachments = attachments
121 @attachments = attachments
122 writeHTMLCell(w, h, x, y,
122 writeHTMLCell(w, h, x, y,
123 fix_text_encoding(
123 fix_text_encoding(
124 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
124 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
125 border, ln, fill)
125 border, ln, fill)
126 end
126 end
127
127
128 def getImageFilename(attrname)
128 def getImageFilename(attrname)
129 # attrname: general_pdf_encoding string file/uri name
129 # attrname: general_pdf_encoding string file/uri name
130 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
130 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
131 if atta
131 if atta
132 return atta.diskfile
132 return atta.diskfile
133 else
133 else
134 return nil
134 return nil
135 end
135 end
136 end
136 end
137
137
138 def Footer
138 def Footer
139 SetFont(@font_for_footer, 'I', 8)
139 SetFont(@font_for_footer, 'I', 8)
140 SetY(-15)
140 SetY(-15)
141 SetX(15)
141 SetX(15)
142 RDMCell(0, 5, @footer_date, 0, 0, 'L')
142 RDMCell(0, 5, @footer_date, 0, 0, 'L')
143 SetY(-15)
143 SetY(-15)
144 SetX(-30)
144 SetX(-30)
145 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
145 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
146 end
146 end
147
147
148 def Bookmark(txt, level=0, y=0)
148 def Bookmark(txt, level=0, y=0)
149 utf16 = Iconv.conv('UTF-16', 'UTF-8', txt)
149 utf16 = Iconv.conv('UTF-16', 'UTF-8', txt)
150 if (y == -1)
150 if (y == -1)
151 y = GetY()
151 y = GetY()
152 end
152 end
153 @outlines << {:t => utf16, :l => level, :p => PageNo(), :y => (@h - y)*@k}
153 @outlines << {:t => utf16, :l => level, :p => PageNo(), :y => (@h - y)*@k}
154 end
154 end
155
155
156 def putbookmarks
156 def putbookmarks
157 nb=@outlines.size
157 nb=@outlines.size
158 return if (nb==0)
158 return if (nb==0)
159 lru=[]
159 lru=[]
160 level=0
160 level=0
161 @outlines.each_with_index do |o, i|
161 @outlines.each_with_index do |o, i|
162 if(o[:l]>0)
162 if(o[:l]>0)
163 parent=lru[o[:l]-1]
163 parent=lru[o[:l]-1]
164 #Set parent and last pointers
164 #Set parent and last pointers
165 @outlines[i][:parent]=parent
165 @outlines[i][:parent]=parent
166 @outlines[parent][:last]=i
166 @outlines[parent][:last]=i
167 if (o[:l]>level)
167 if (o[:l]>level)
168 #Level increasing: set first pointer
168 #Level increasing: set first pointer
169 @outlines[parent][:first]=i
169 @outlines[parent][:first]=i
170 end
170 end
171 else
171 else
172 @outlines[i][:parent]=nb
172 @outlines[i][:parent]=nb
173 end
173 end
174 if (o[:l]<=level && i>0)
174 if (o[:l]<=level && i>0)
175 #Set prev and next pointers
175 #Set prev and next pointers
176 prev=lru[o[:l]]
176 prev=lru[o[:l]]
177 @outlines[prev][:next]=i
177 @outlines[prev][:next]=i
178 @outlines[i][:prev]=prev
178 @outlines[i][:prev]=prev
179 end
179 end
180 lru[o[:l]]=i
180 lru[o[:l]]=i
181 level=o[:l]
181 level=o[:l]
182 end
182 end
183 #Outline items
183 #Outline items
184 n=self.n+1
184 n=self.n+1
185 @outlines.each_with_index do |o, i|
185 @outlines.each_with_index do |o, i|
186 newobj()
186 newobj()
187 out('<</Title '+textstring(o[:t]))
187 out('<</Title '+textstring(o[:t]))
188 out("/Parent #{n+o[:parent]} 0 R")
188 out("/Parent #{n+o[:parent]} 0 R")
189 if (o[:prev])
189 if (o[:prev])
190 out("/Prev #{n+o[:prev]} 0 R")
190 out("/Prev #{n+o[:prev]} 0 R")
191 end
191 end
192 if (o[:next])
192 if (o[:next])
193 out("/Next #{n+o[:next]} 0 R")
193 out("/Next #{n+o[:next]} 0 R")
194 end
194 end
195 if (o[:first])
195 if (o[:first])
196 out("/First #{n+o[:first]} 0 R")
196 out("/First #{n+o[:first]} 0 R")
197 end
197 end
198 if (o[:last])
198 if (o[:last])
199 out("/Last #{n+o[:last]} 0 R")
199 out("/Last #{n+o[:last]} 0 R")
200 end
200 end
201 out("/Dest [%d 0 R /XYZ 0 %.2f null]" % [1+2*o[:p], o[:y]])
201 out("/Dest [%d 0 R /XYZ 0 %.2f null]" % [1+2*o[:p], o[:y]])
202 out('/Count 0>>')
202 out('/Count 0>>')
203 out('endobj')
203 out('endobj')
204 end
204 end
205 #Outline root
205 #Outline root
206 newobj()
206 newobj()
207 @outlineRoot=self.n
207 @outlineRoot=self.n
208 out("<</Type /Outlines /First #{n} 0 R");
208 out("<</Type /Outlines /First #{n} 0 R");
209 out("/Last #{n+lru[0]} 0 R>>");
209 out("/Last #{n+lru[0]} 0 R>>");
210 out('endobj');
210 out('endobj');
211 end
211 end
212
212
213 def putresources()
213 def putresources()
214 super
214 super
215 putbookmarks()
215 putbookmarks()
216 end
216 end
217
217
218 def putcatalog()
218 def putcatalog()
219 super
219 super
220 if(@outlines.size > 0)
220 if(@outlines.size > 0)
221 out("/Outlines #{@outlineRoot} 0 R");
221 out("/Outlines #{@outlineRoot} 0 R");
222 out('/PageMode /UseOutlines');
222 out('/PageMode /UseOutlines');
223 end
223 end
224 end
224 end
225 end
225 end
226
226
227 # Returns a PDF string of a list of issues
227 # Returns a PDF string of a list of issues
228 def issues_to_pdf(issues, project, query)
228 def issues_to_pdf(issues, project, query)
229 pdf = ITCPDF.new(current_language)
229 pdf = ITCPDF.new(current_language)
230 title = query.new_record? ? l(:label_issue_plural) : query.name
230 title = query.new_record? ? l(:label_issue_plural) : query.name
231 title = "#{project} - #{title}" if project
231 title = "#{project} - #{title}" if project
232 pdf.SetTitle(title)
232 pdf.SetTitle(title)
233 pdf.alias_nb_pages
233 pdf.alias_nb_pages
234 pdf.footer_date = format_date(Date.today)
234 pdf.footer_date = format_date(Date.today)
235 pdf.SetAutoPageBreak(false)
235 pdf.SetAutoPageBreak(false)
236 pdf.AddPage("L")
236 pdf.AddPage("L")
237
237
238 # Landscape A4 = 210 x 297 mm
238 # Landscape A4 = 210 x 297 mm
239 page_height = 210
239 page_height = 210
240 page_width = 297
240 page_width = 297
241 right_margin = 10
241 right_margin = 10
242 bottom_margin = 20
242 bottom_margin = 20
243 col_id_width = 10
243 col_id_width = 10
244 row_height = 5
244 row_height = 5
245
245
246 # column widths
246 # column widths
247 table_width = page_width - right_margin - 10 # fixed left margin
247 table_width = page_width - right_margin - 10 # fixed left margin
248 col_width = []
248 col_width = []
249 unless query.columns.empty?
249 unless query.columns.empty?
250 col_width = query.columns.collect do |c|
250 col_width = query.columns.collect do |c|
251 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) &&
251 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) &&
252 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0
252 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0
253 end
253 end
254 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
254 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
255 col_width = col_width.collect {|w| w * ratio}
255 col_width = col_width.collect {|w| w * ratio}
256 end
256 end
257
257
258 # title
258 # title
259 pdf.SetFontStyle('B',11)
259 pdf.SetFontStyle('B',11)
260 pdf.RDMCell(190,10, title)
260 pdf.RDMCell(190,10, title)
261 pdf.Ln
261 pdf.Ln
262
262
263 # headers
263 # headers
264 pdf.SetFontStyle('B',8)
264 pdf.SetFontStyle('B',8)
265 pdf.SetFillColor(230, 230, 230)
265 pdf.SetFillColor(230, 230, 230)
266
266
267 # render it background to find the max height used
267 # render it background to find the max height used
268 base_x = pdf.GetX
268 base_x = pdf.GetX
269 base_y = pdf.GetY
269 base_y = pdf.GetY
270 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
270 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
271 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
271 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
272 pdf.SetXY(base_x, base_y);
272 pdf.SetXY(base_x, base_y);
273
273
274 # write the cells on page
274 # write the cells on page
275 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
275 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
276 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
276 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
277 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
277 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
278 pdf.SetY(base_y + max_height);
278 pdf.SetY(base_y + max_height);
279
279
280 # rows
280 # rows
281 pdf.SetFontStyle('',8)
281 pdf.SetFontStyle('',8)
282 pdf.SetFillColor(255, 255, 255)
282 pdf.SetFillColor(255, 255, 255)
283 previous_group = false
283 previous_group = false
284 issue_list(issues) do |issue, level|
284 issue_list(issues) do |issue, level|
285 if query.grouped? &&
285 if query.grouped? &&
286 (group = query.group_by_column.value(issue)) != previous_group
286 (group = query.group_by_column.value(issue)) != previous_group
287 pdf.SetFontStyle('B',9)
287 pdf.SetFontStyle('B',9)
288 group_label = group.blank? ? 'None' : group.to_s
288 group_label = group.blank? ? 'None' : group.to_s
289 group_label << " (#{query.issue_count_by_group[group]})"
289 group_label << " (#{query.issue_count_by_group[group]})"
290 pdf.Bookmark group_label, 0, -1
290 pdf.Bookmark group_label, 0, -1
291 pdf.RDMCell(277, row_height, group_label, 1, 1, 'L')
291 pdf.RDMCell(277, row_height, group_label, 1, 1, 'L')
292 pdf.SetFontStyle('',8)
292 pdf.SetFontStyle('',8)
293 previous_group = group
293 previous_group = group
294 end
294 end
295 # fetch all the row values
295 # fetch all the row values
296 col_values = query.columns.collect do |column|
296 col_values = query.columns.collect do |column|
297 s = if column.is_a?(QueryCustomFieldColumn)
297 s = if column.is_a?(QueryCustomFieldColumn)
298 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
298 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
299 show_value(cv)
299 show_value(cv)
300 else
300 else
301 value = issue.send(column.name)
301 value = issue.send(column.name)
302 if column.name == :subject
302 if column.name == :subject
303 value = " " * level + value
303 value = " " * level + value
304 end
304 end
305 if value.is_a?(Date)
305 if value.is_a?(Date)
306 format_date(value)
306 format_date(value)
307 elsif value.is_a?(Time)
307 elsif value.is_a?(Time)
308 format_time(value)
308 format_time(value)
309 else
309 else
310 value
310 value
311 end
311 end
312 end
312 end
313 s.to_s
313 s.to_s
314 end
314 end
315
315
316 # render it off-page to find the max height used
316 # render it off-page to find the max height used
317 base_x = pdf.GetX
317 base_x = pdf.GetX
318 base_y = pdf.GetY
318 base_y = pdf.GetY
319 pdf.SetY(2 * page_height)
319 pdf.SetY(2 * page_height)
320 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
320 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
321 pdf.SetXY(base_x, base_y)
321 pdf.SetXY(base_x, base_y)
322
322
323 # make new page if it doesn't fit on the current one
323 # make new page if it doesn't fit on the current one
324 space_left = page_height - base_y - bottom_margin
324 space_left = page_height - base_y - bottom_margin
325 if max_height > space_left
325 if max_height > space_left
326 pdf.AddPage("L")
326 pdf.AddPage("L")
327 base_x = pdf.GetX
327 base_x = pdf.GetX
328 base_y = pdf.GetY
328 base_y = pdf.GetY
329 end
329 end
330
330
331 # write the cells on page
331 # write the cells on page
332 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
332 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
333 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
333 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
334 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
334 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
335 pdf.SetY(base_y + max_height);
335 pdf.SetY(base_y + max_height);
336 end
336 end
337
337
338 if issues.size == Setting.issues_export_limit.to_i
338 if issues.size == Setting.issues_export_limit.to_i
339 pdf.SetFontStyle('B',10)
339 pdf.SetFontStyle('B',10)
340 pdf.RDMCell(0, row_height, '...')
340 pdf.RDMCell(0, row_height, '...')
341 end
341 end
342 pdf.Output
342 pdf.Output
343 end
343 end
344
344
345 # Renders MultiCells and returns the maximum height used
345 # Renders MultiCells and returns the maximum height used
346 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
346 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
347 row_height, head=false)
347 row_height, head=false)
348 base_y = pdf.GetY
348 base_y = pdf.GetY
349 max_height = row_height
349 max_height = row_height
350 col_values.each_with_index do |column, i|
350 col_values.each_with_index do |column, i|
351 col_x = pdf.GetX
351 col_x = pdf.GetX
352 if head == true
352 if head == true
353 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
353 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
354 else
354 else
355 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
355 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
356 end
356 end
357 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
357 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
358 pdf.SetXY(col_x + col_widths[i], base_y);
358 pdf.SetXY(col_x + col_widths[i], base_y);
359 end
359 end
360 return max_height
360 return max_height
361 end
361 end
362
362
363 # Draw lines to close the row (MultiCell border drawing in not uniform)
363 # Draw lines to close the row (MultiCell border drawing in not uniform)
364 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
364 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
365 id_width, col_widths)
365 id_width, col_widths)
366 col_x = top_x + id_width
366 col_x = top_x + id_width
367 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
367 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
368 col_widths.each do |width|
368 col_widths.each do |width|
369 col_x += width
369 col_x += width
370 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
370 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
371 end
371 end
372 pdf.Line(top_x, top_y, top_x, lower_y) # left border
372 pdf.Line(top_x, top_y, top_x, lower_y) # left border
373 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
373 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
374 end
374 end
375
375
376 # Returns a PDF string of a single issue
376 # Returns a PDF string of a single issue
377 def issue_to_pdf(issue)
377 def issue_to_pdf(issue)
378 pdf = ITCPDF.new(current_language)
378 pdf = ITCPDF.new(current_language)
379 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
379 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
380 pdf.alias_nb_pages
380 pdf.alias_nb_pages
381 pdf.footer_date = format_date(Date.today)
381 pdf.footer_date = format_date(Date.today)
382 pdf.AddPage
382 pdf.AddPage
383 pdf.SetFontStyle('B',11)
383 pdf.SetFontStyle('B',11)
384 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
384 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
385 pdf.RDMMultiCell(190, 5, buf)
385 pdf.RDMMultiCell(190, 5, buf)
386 pdf.Ln
386 pdf.Ln
387 pdf.SetFontStyle('',8)
387 pdf.SetFontStyle('',8)
388 base_x = pdf.GetX
388 base_x = pdf.GetX
389 i = 1
389 i = 1
390 issue.ancestors.each do |ancestor|
390 issue.ancestors.each do |ancestor|
391 pdf.SetX(base_x + i)
391 pdf.SetX(base_x + i)
392 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
392 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
393 pdf.RDMMultiCell(190 - i, 5, buf)
393 pdf.RDMMultiCell(190 - i, 5, buf)
394 i += 1 if i < 35
394 i += 1 if i < 35
395 end
395 end
396 pdf.Ln
396 pdf.Ln
397
397
398 pdf.SetFontStyle('B',9)
398 pdf.SetFontStyle('B',9)
399 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
399 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
400 pdf.SetFontStyle('',9)
400 pdf.SetFontStyle('',9)
401 pdf.RDMCell(60,5, issue.status.to_s,"RT")
401 pdf.RDMCell(60,5, issue.status.to_s,"RT")
402 pdf.SetFontStyle('B',9)
402 pdf.SetFontStyle('B',9)
403 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
403 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
404 pdf.SetFontStyle('',9)
404 pdf.SetFontStyle('',9)
405 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
405 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
406 pdf.Ln
406 pdf.Ln
407
407
408 pdf.SetFontStyle('B',9)
408 pdf.SetFontStyle('B',9)
409 pdf.RDMCell(35,5, l(:field_author) + ":","L")
409 pdf.RDMCell(35,5, l(:field_author) + ":","L")
410 pdf.SetFontStyle('',9)
410 pdf.SetFontStyle('',9)
411 pdf.RDMCell(60,5, issue.author.to_s,"R")
411 pdf.RDMCell(60,5, issue.author.to_s,"R")
412 pdf.SetFontStyle('B',9)
412 pdf.SetFontStyle('B',9)
413 pdf.RDMCell(35,5, l(:field_category) + ":","L")
413 pdf.RDMCell(35,5, l(:field_category) + ":","L")
414 pdf.SetFontStyle('',9)
414 pdf.SetFontStyle('',9)
415 pdf.RDMCell(60,5, issue.category.to_s,"R")
415 pdf.RDMCell(60,5, issue.category.to_s,"R")
416 pdf.Ln
416 pdf.Ln
417
417
418 pdf.SetFontStyle('B',9)
418 pdf.SetFontStyle('B',9)
419 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
419 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
420 pdf.SetFontStyle('',9)
420 pdf.SetFontStyle('',9)
421 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
421 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
422 pdf.SetFontStyle('B',9)
422 pdf.SetFontStyle('B',9)
423 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
423 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
424 pdf.SetFontStyle('',9)
424 pdf.SetFontStyle('',9)
425 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
425 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
426 pdf.Ln
426 pdf.Ln
427
427
428 pdf.SetFontStyle('B',9)
428 pdf.SetFontStyle('B',9)
429 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
429 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
430 pdf.SetFontStyle('',9)
430 pdf.SetFontStyle('',9)
431 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
431 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
432 pdf.SetFontStyle('B',9)
432 pdf.SetFontStyle('B',9)
433 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
433 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
434 pdf.SetFontStyle('',9)
434 pdf.SetFontStyle('',9)
435 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
435 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
436 pdf.Ln
436 pdf.Ln
437
437
438 for custom_value in issue.custom_field_values
438 for custom_value in issue.custom_field_values
439 pdf.SetFontStyle('B',9)
439 pdf.SetFontStyle('B',9)
440 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
440 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
441 pdf.SetFontStyle('',9)
441 pdf.SetFontStyle('',9)
442 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
442 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
443 end
443 end
444
444
445 y0 = pdf.GetY
445 y0 = pdf.GetY
446
446
447 pdf.SetFontStyle('B',9)
447 pdf.SetFontStyle('B',9)
448 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
448 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
449 pdf.SetFontStyle('',9)
449 pdf.SetFontStyle('',9)
450 pdf.RDMMultiCell(155,5, issue.subject,"RT")
450 pdf.RDMMultiCell(155,5, issue.subject,"RT")
451 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
451 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
452
452
453 pdf.SetFontStyle('B',9)
453 pdf.SetFontStyle('B',9)
454 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
454 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
455 pdf.SetFontStyle('',9)
455 pdf.SetFontStyle('',9)
456
456
457 # Set resize image scale
457 # Set resize image scale
458 pdf.SetImageScale(1.6)
458 pdf.SetImageScale(1.6)
459 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
459 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
460 issue.description.to_s, issue.attachments, "LRB")
460 issue.description.to_s, issue.attachments, "LRB")
461
461
462 unless issue.leaf?
462 unless issue.leaf?
463 # for CJK
463 # for CJK
464 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
464 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
465
465
466 pdf.SetFontStyle('B',9)
466 pdf.SetFontStyle('B',9)
467 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
467 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
468 pdf.Ln
468 pdf.Ln
469 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
469 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
470 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
470 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
471 :length => truncate_length)
471 :length => truncate_length)
472 level = 10 if level >= 10
472 level = 10 if level >= 10
473 pdf.SetFontStyle('',8)
473 pdf.SetFontStyle('',8)
474 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
474 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
475 pdf.SetFontStyle('B',8)
475 pdf.SetFontStyle('B',8)
476 pdf.RDMCell(20,5, child.status.to_s, "R")
476 pdf.RDMCell(20,5, child.status.to_s, "R")
477 pdf.Ln
477 pdf.Ln
478 end
478 end
479 end
479 end
480
480
481 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
481 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
482 unless relations.empty?
482 unless relations.empty?
483 # for CJK
483 # for CJK
484 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
484 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
485
485
486 pdf.SetFontStyle('B',9)
486 pdf.SetFontStyle('B',9)
487 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
487 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
488 pdf.Ln
488 pdf.Ln
489 relations.each do |relation|
489 relations.each do |relation|
490 buf = ""
490 buf = ""
491 buf += "#{l(relation.label_for(issue))} "
491 buf += "#{l(relation.label_for(issue))} "
492 if relation.delay && relation.delay != 0
492 if relation.delay && relation.delay != 0
493 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
493 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
494 end
494 end
495 if Setting.cross_project_issue_relations?
495 if Setting.cross_project_issue_relations?
496 buf += "#{relation.other_issue(issue).project} - "
496 buf += "#{relation.other_issue(issue).project} - "
497 end
497 end
498 buf += "#{relation.other_issue(issue).tracker}" +
498 buf += "#{relation.other_issue(issue).tracker}" +
499 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
499 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
500 buf = truncate(buf, :length => truncate_length)
500 buf = truncate(buf, :length => truncate_length)
501 pdf.SetFontStyle('', 8)
501 pdf.SetFontStyle('', 8)
502 pdf.RDMCell(35+155-60, 5, buf, "L")
502 pdf.RDMCell(35+155-60, 5, buf, "L")
503 pdf.SetFontStyle('B',8)
503 pdf.SetFontStyle('B',8)
504 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
504 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
505 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
505 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
506 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
506 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
507 pdf.Ln
507 pdf.Ln
508 end
508 end
509 end
509 end
510 pdf.RDMCell(190,5, "", "T")
510 pdf.RDMCell(190,5, "", "T")
511 pdf.Ln
511 pdf.Ln
512
512
513 if issue.changesets.any? &&
513 if issue.changesets.any? &&
514 User.current.allowed_to?(:view_changesets, issue.project)
514 User.current.allowed_to?(:view_changesets, issue.project)
515 pdf.SetFontStyle('B',9)
515 pdf.SetFontStyle('B',9)
516 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
516 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
517 pdf.Ln
517 pdf.Ln
518 for changeset in issue.changesets
518 for changeset in issue.changesets
519 pdf.SetFontStyle('B',8)
519 pdf.SetFontStyle('B',8)
520 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
520 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
521 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
521 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
522 pdf.RDMCell(190, 5, csstr)
522 pdf.RDMCell(190, 5, csstr)
523 pdf.Ln
523 pdf.Ln
524 unless changeset.comments.blank?
524 unless changeset.comments.blank?
525 pdf.SetFontStyle('',8)
525 pdf.SetFontStyle('',8)
526 pdf.RDMwriteHTMLCell(190,5,0,0,
526 pdf.RDMwriteHTMLCell(190,5,0,0,
527 changeset.comments.to_s, issue.attachments, "")
527 changeset.comments.to_s, issue.attachments, "")
528 end
528 end
529 pdf.Ln
529 pdf.Ln
530 end
530 end
531 end
531 end
532
532
533 pdf.SetFontStyle('B',9)
533 pdf.SetFontStyle('B',9)
534 pdf.RDMCell(190,5, l(:label_history), "B")
534 pdf.RDMCell(190,5, l(:label_history), "B")
535 pdf.Ln
535 pdf.Ln
536 indice = 0
536 indice = 0
537 for journal in issue.journals.find(
537 for journal in issue.journals.find(
538 :all, :include => [:user, :details],
538 :all, :include => [:user, :details],
539 :order => "#{Journal.table_name}.created_on ASC")
539 :order => "#{Journal.table_name}.created_on ASC")
540 indice = indice + 1
540 indice = indice + 1
541 pdf.SetFontStyle('B',8)
541 pdf.SetFontStyle('B',8)
542 pdf.RDMCell(190,5,
542 pdf.RDMCell(190,5,
543 "#" + indice.to_s +
543 "#" + indice.to_s +
544 " - " + format_time(journal.created_on) +
544 " - " + format_time(journal.created_on) +
545 " - " + journal.user.name)
545 " - " + journal.user.name)
546 pdf.Ln
546 pdf.Ln
547 pdf.SetFontStyle('I',8)
547 pdf.SetFontStyle('I',8)
548 details_to_strings(journal.details, true).each do |string|
548 details_to_strings(journal.details, true).each do |string|
549 pdf.RDMMultiCell(190,5, "- " + string)
549 pdf.RDMMultiCell(190,5, "- " + string)
550 end
550 end
551 if journal.notes?
551 if journal.notes?
552 pdf.Ln unless journal.details.empty?
552 pdf.Ln unless journal.details.empty?
553 pdf.SetFontStyle('',8)
553 pdf.SetFontStyle('',8)
554 pdf.RDMwriteHTMLCell(190,5,0,0,
554 pdf.RDMwriteHTMLCell(190,5,0,0,
555 journal.notes.to_s, issue.attachments, "")
555 journal.notes.to_s, issue.attachments, "")
556 end
556 end
557 pdf.Ln
557 pdf.Ln
558 end
558 end
559
559
560 if issue.attachments.any?
560 if issue.attachments.any?
561 pdf.SetFontStyle('B',9)
561 pdf.SetFontStyle('B',9)
562 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
562 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
563 pdf.Ln
563 pdf.Ln
564 for attachment in issue.attachments
564 for attachment in issue.attachments
565 pdf.SetFontStyle('',8)
565 pdf.SetFontStyle('',8)
566 pdf.RDMCell(80,5, attachment.filename)
566 pdf.RDMCell(80,5, attachment.filename)
567 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
567 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
568 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
568 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
569 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
569 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
570 pdf.Ln
570 pdf.Ln
571 end
571 end
572 end
572 end
573 pdf.Output
573 pdf.Output
574 end
574 end
575
575
576 # Returns a PDF string of a set of wiki pages
577 def wiki_pages_to_pdf(pages, project)
578 pdf = ITCPDF.new(current_language)
579 pdf.SetTitle(project.name)
580 pdf.alias_nb_pages
581 pdf.footer_date = format_date(Date.today)
582 pdf.AddPage
583 pdf.SetFontStyle('B',11)
584 pdf.RDMMultiCell(190,5, project.name)
585 pdf.Ln
586 # Set resize image scale
587 pdf.SetImageScale(1.6)
588 pdf.SetFontStyle('',9)
589 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
590 pdf.Output
591 end
592
576 # Returns a PDF string of a single wiki page
593 # Returns a PDF string of a single wiki page
577 def wiki_to_pdf(page, project)
594 def wiki_page_to_pdf(page, project)
578 pdf = ITCPDF.new(current_language)
595 pdf = ITCPDF.new(current_language)
579 pdf.SetTitle("#{project} - #{page.title}")
596 pdf.SetTitle("#{project} - #{page.title}")
580 pdf.alias_nb_pages
597 pdf.alias_nb_pages
581 pdf.footer_date = format_date(Date.today)
598 pdf.footer_date = format_date(Date.today)
582 pdf.AddPage
599 pdf.AddPage
583 pdf.SetFontStyle('B',11)
600 pdf.SetFontStyle('B',11)
584 pdf.RDMMultiCell(190,5,
601 pdf.RDMMultiCell(190,5,
585 "#{project} - #{page.title} - # #{page.content.version}")
602 "#{project} - #{page.title} - # #{page.content.version}")
586 pdf.Ln
603 pdf.Ln
587 # Set resize image scale
604 # Set resize image scale
588 pdf.SetImageScale(1.6)
605 pdf.SetImageScale(1.6)
589 pdf.SetFontStyle('',9)
606 pdf.SetFontStyle('',9)
607 write_wiki_page(pdf, page)
608 pdf.Output
609 end
610
611 def write_page_hierarchy(pdf, pages, node=nil, level=0)
612 if pages[node]
613 pages[node].each do |page|
614 if @new_page
615 pdf.AddPage
616 else
617 @new_page = true
618 end
619 pdf.Bookmark page.title, level
620 write_wiki_page(pdf, page)
621 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
622 end
623 end
624 end
625
626 def write_wiki_page(pdf, page)
590 pdf.RDMwriteHTMLCell(190,5,0,0,
627 pdf.RDMwriteHTMLCell(190,5,0,0,
591 page.content.text.to_s, page.attachments, "TLRB")
628 page.content.text.to_s, page.attachments, "TLRB")
592 if page.attachments.any?
629 if page.attachments.any?
593 pdf.Ln
630 pdf.Ln
594 pdf.SetFontStyle('B',9)
631 pdf.SetFontStyle('B',9)
595 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
632 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
596 pdf.Ln
633 pdf.Ln
597 for attachment in page.attachments
634 for attachment in page.attachments
598 pdf.SetFontStyle('',8)
635 pdf.SetFontStyle('',8)
599 pdf.RDMCell(80,5, attachment.filename)
636 pdf.RDMCell(80,5, attachment.filename)
600 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
637 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
601 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
638 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
602 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
639 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
603 pdf.Ln
640 pdf.Ln
604 end
641 end
605 end
642 end
606 pdf.Output
607 end
643 end
608
644
609 class RDMPdfEncoding
645 class RDMPdfEncoding
610 def self.rdm_from_utf8(txt, encoding)
646 def self.rdm_from_utf8(txt, encoding)
611 txt ||= ''
647 txt ||= ''
612 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
648 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
613 if txt.respond_to?(:force_encoding)
649 if txt.respond_to?(:force_encoding)
614 txt.force_encoding('ASCII-8BIT')
650 txt.force_encoding('ASCII-8BIT')
615 end
651 end
616 txt
652 txt
617 end
653 end
618
654
619 def self.attach(attachments, filename, encoding)
655 def self.attach(attachments, filename, encoding)
620 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
656 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
621 atta = nil
657 atta = nil
622 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
658 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
623 atta = Attachment.latest_attach(attachments, filename_utf8)
659 atta = Attachment.latest_attach(attachments, filename_utf8)
624 end
660 end
625 if atta && atta.readable? && atta.visible?
661 if atta && atta.readable? && atta.visible?
626 return atta
662 return atta
627 else
663 else
628 return nil
664 return nil
629 end
665 end
630 end
666 end
631 end
667 end
632 end
668 end
633 end
669 end
634 end
670 end
@@ -1,756 +1,769
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'wiki_controller'
19 require 'wiki_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class WikiController; def rescue_action(e) raise e end; end
22 class WikiController; def rescue_action(e) raise e end; end
23
23
24 class WikiControllerTest < ActionController::TestCase
24 class WikiControllerTest < ActionController::TestCase
25 fixtures :projects, :users, :roles, :members, :member_roles,
25 fixtures :projects, :users, :roles, :members, :member_roles,
26 :enabled_modules, :wikis, :wiki_pages, :wiki_contents,
26 :enabled_modules, :wikis, :wiki_pages, :wiki_contents,
27 :wiki_content_versions, :attachments
27 :wiki_content_versions, :attachments
28
28
29 def setup
29 def setup
30 @controller = WikiController.new
30 @controller = WikiController.new
31 @request = ActionController::TestRequest.new
31 @request = ActionController::TestRequest.new
32 @response = ActionController::TestResponse.new
32 @response = ActionController::TestResponse.new
33 User.current = nil
33 User.current = nil
34 end
34 end
35
35
36 def test_show_start_page
36 def test_show_start_page
37 get :show, :project_id => 'ecookbook'
37 get :show, :project_id => 'ecookbook'
38 assert_response :success
38 assert_response :success
39 assert_template 'show'
39 assert_template 'show'
40 assert_tag :tag => 'h1', :content => /CookBook documentation/
40 assert_tag :tag => 'h1', :content => /CookBook documentation/
41
41
42 # child_pages macro
42 # child_pages macro
43 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
43 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
44 :child => { :tag => 'li',
44 :child => { :tag => 'li',
45 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
45 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
46 :content => 'Page with an inline image' } }
46 :content => 'Page with an inline image' } }
47 end
47 end
48
48
49 def test_export_link
49 def test_export_link
50 Role.anonymous.add_permission! :export_wiki_pages
50 Role.anonymous.add_permission! :export_wiki_pages
51 get :show, :project_id => 'ecookbook'
51 get :show, :project_id => 'ecookbook'
52 assert_response :success
52 assert_response :success
53 assert_tag 'a', :attributes => {:href => '/projects/ecookbook/wiki/CookBook_documentation.txt'}
53 assert_tag 'a', :attributes => {:href => '/projects/ecookbook/wiki/CookBook_documentation.txt'}
54 end
54 end
55
55
56 def test_show_page_with_name
56 def test_show_page_with_name
57 get :show, :project_id => 1, :id => 'Another_page'
57 get :show, :project_id => 1, :id => 'Another_page'
58 assert_response :success
58 assert_response :success
59 assert_template 'show'
59 assert_template 'show'
60 assert_tag :tag => 'h1', :content => /Another page/
60 assert_tag :tag => 'h1', :content => /Another page/
61 # Included page with an inline image
61 # Included page with an inline image
62 assert_tag :tag => 'p', :content => /This is an inline image/
62 assert_tag :tag => 'p', :content => /This is an inline image/
63 assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3',
63 assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3',
64 :alt => 'This is a logo' }
64 :alt => 'This is a logo' }
65 end
65 end
66
66
67 def test_show_redirected_page
67 def test_show_redirected_page
68 WikiRedirect.create!(:wiki_id => 1, :title => 'Old_title', :redirects_to => 'Another_page')
68 WikiRedirect.create!(:wiki_id => 1, :title => 'Old_title', :redirects_to => 'Another_page')
69
69
70 get :show, :project_id => 'ecookbook', :id => 'Old_title'
70 get :show, :project_id => 'ecookbook', :id => 'Old_title'
71 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
71 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
72 end
72 end
73
73
74 def test_show_with_sidebar
74 def test_show_with_sidebar
75 page = Project.find(1).wiki.pages.new(:title => 'Sidebar')
75 page = Project.find(1).wiki.pages.new(:title => 'Sidebar')
76 page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar')
76 page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar')
77 page.save!
77 page.save!
78
78
79 get :show, :project_id => 1, :id => 'Another_page'
79 get :show, :project_id => 1, :id => 'Another_page'
80 assert_response :success
80 assert_response :success
81 assert_tag :tag => 'div', :attributes => {:id => 'sidebar'},
81 assert_tag :tag => 'div', :attributes => {:id => 'sidebar'},
82 :content => /Side bar content for test_show_with_sidebar/
82 :content => /Side bar content for test_show_with_sidebar/
83 end
83 end
84
84
85 def test_show_should_display_section_edit_links
85 def test_show_should_display_section_edit_links
86 @request.session[:user_id] = 2
86 @request.session[:user_id] = 2
87 get :show, :project_id => 1, :id => 'Page with sections'
87 get :show, :project_id => 1, :id => 'Page with sections'
88 assert_no_tag 'a', :attributes => {
88 assert_no_tag 'a', :attributes => {
89 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=1'
89 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=1'
90 }
90 }
91 assert_tag 'a', :attributes => {
91 assert_tag 'a', :attributes => {
92 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
92 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
93 }
93 }
94 assert_tag 'a', :attributes => {
94 assert_tag 'a', :attributes => {
95 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=3'
95 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=3'
96 }
96 }
97 end
97 end
98
98
99 def test_show_current_version_should_display_section_edit_links
99 def test_show_current_version_should_display_section_edit_links
100 @request.session[:user_id] = 2
100 @request.session[:user_id] = 2
101 get :show, :project_id => 1, :id => 'Page with sections', :version => 3
101 get :show, :project_id => 1, :id => 'Page with sections', :version => 3
102
102
103 assert_tag 'a', :attributes => {
103 assert_tag 'a', :attributes => {
104 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
104 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
105 }
105 }
106 end
106 end
107
107
108 def test_show_old_version_should_not_display_section_edit_links
108 def test_show_old_version_should_not_display_section_edit_links
109 @request.session[:user_id] = 2
109 @request.session[:user_id] = 2
110 get :show, :project_id => 1, :id => 'Page with sections', :version => 2
110 get :show, :project_id => 1, :id => 'Page with sections', :version => 2
111
111
112 assert_no_tag 'a', :attributes => {
112 assert_no_tag 'a', :attributes => {
113 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
113 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
114 }
114 }
115 end
115 end
116
116
117 def test_show_unexistent_page_without_edit_right
117 def test_show_unexistent_page_without_edit_right
118 get :show, :project_id => 1, :id => 'Unexistent page'
118 get :show, :project_id => 1, :id => 'Unexistent page'
119 assert_response 404
119 assert_response 404
120 end
120 end
121
121
122 def test_show_unexistent_page_with_edit_right
122 def test_show_unexistent_page_with_edit_right
123 @request.session[:user_id] = 2
123 @request.session[:user_id] = 2
124 get :show, :project_id => 1, :id => 'Unexistent page'
124 get :show, :project_id => 1, :id => 'Unexistent page'
125 assert_response :success
125 assert_response :success
126 assert_template 'edit'
126 assert_template 'edit'
127 assert_no_tag 'input', :attributes => {:name => 'page[parent_id]'}
127 assert_no_tag 'input', :attributes => {:name => 'page[parent_id]'}
128 end
128 end
129
129
130 def test_show_unexistent_page_with_parent
130 def test_show_unexistent_page_with_parent
131 @request.session[:user_id] = 2
131 @request.session[:user_id] = 2
132 get :show, :project_id => 1, :id => 'Unexistent page', :parent => 'Another_page'
132 get :show, :project_id => 1, :id => 'Unexistent page', :parent => 'Another_page'
133 assert_response :success
133 assert_response :success
134 assert_template 'edit'
134 assert_template 'edit'
135 assert_tag 'input', :attributes => {:name => 'page[parent_id]', :value => '2'}
135 assert_tag 'input', :attributes => {:name => 'page[parent_id]', :value => '2'}
136 end
136 end
137
137
138 def test_show_should_not_show_history_without_permission
138 def test_show_should_not_show_history_without_permission
139 Role.anonymous.remove_permission! :view_wiki_edits
139 Role.anonymous.remove_permission! :view_wiki_edits
140 get :show, :project_id => 1, :id => 'Page with sections', :version => 2
140 get :show, :project_id => 1, :id => 'Page with sections', :version => 2
141
141
142 assert_response 302
142 assert_response 302
143 end
143 end
144
144
145 def test_create_page
145 def test_create_page
146 @request.session[:user_id] = 2
146 @request.session[:user_id] = 2
147 assert_difference 'WikiPage.count' do
147 assert_difference 'WikiPage.count' do
148 assert_difference 'WikiContent.count' do
148 assert_difference 'WikiContent.count' do
149 put :update, :project_id => 1,
149 put :update, :project_id => 1,
150 :id => 'New page',
150 :id => 'New page',
151 :content => {:comments => 'Created the page',
151 :content => {:comments => 'Created the page',
152 :text => "h1. New page\n\nThis is a new page",
152 :text => "h1. New page\n\nThis is a new page",
153 :version => 0}
153 :version => 0}
154 end
154 end
155 end
155 end
156 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
156 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
157 page = Project.find(1).wiki.find_page('New page')
157 page = Project.find(1).wiki.find_page('New page')
158 assert !page.new_record?
158 assert !page.new_record?
159 assert_not_nil page.content
159 assert_not_nil page.content
160 assert_nil page.parent
160 assert_nil page.parent
161 assert_equal 'Created the page', page.content.comments
161 assert_equal 'Created the page', page.content.comments
162 end
162 end
163
163
164 def test_create_page_with_attachments
164 def test_create_page_with_attachments
165 @request.session[:user_id] = 2
165 @request.session[:user_id] = 2
166 assert_difference 'WikiPage.count' do
166 assert_difference 'WikiPage.count' do
167 assert_difference 'Attachment.count' do
167 assert_difference 'Attachment.count' do
168 put :update, :project_id => 1,
168 put :update, :project_id => 1,
169 :id => 'New page',
169 :id => 'New page',
170 :content => {:comments => 'Created the page',
170 :content => {:comments => 'Created the page',
171 :text => "h1. New page\n\nThis is a new page",
171 :text => "h1. New page\n\nThis is a new page",
172 :version => 0},
172 :version => 0},
173 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
173 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
174 end
174 end
175 end
175 end
176 page = Project.find(1).wiki.find_page('New page')
176 page = Project.find(1).wiki.find_page('New page')
177 assert_equal 1, page.attachments.count
177 assert_equal 1, page.attachments.count
178 assert_equal 'testfile.txt', page.attachments.first.filename
178 assert_equal 'testfile.txt', page.attachments.first.filename
179 end
179 end
180
180
181 def test_create_page_with_parent
181 def test_create_page_with_parent
182 @request.session[:user_id] = 2
182 @request.session[:user_id] = 2
183 assert_difference 'WikiPage.count' do
183 assert_difference 'WikiPage.count' do
184 put :update, :project_id => 1, :id => 'New page',
184 put :update, :project_id => 1, :id => 'New page',
185 :content => {:text => "h1. New page\n\nThis is a new page", :version => 0},
185 :content => {:text => "h1. New page\n\nThis is a new page", :version => 0},
186 :page => {:parent_id => 2}
186 :page => {:parent_id => 2}
187 end
187 end
188 page = Project.find(1).wiki.find_page('New page')
188 page = Project.find(1).wiki.find_page('New page')
189 assert_equal WikiPage.find(2), page.parent
189 assert_equal WikiPage.find(2), page.parent
190 end
190 end
191
191
192 def test_edit_page
192 def test_edit_page
193 @request.session[:user_id] = 2
193 @request.session[:user_id] = 2
194 get :edit, :project_id => 'ecookbook', :id => 'Another_page'
194 get :edit, :project_id => 'ecookbook', :id => 'Another_page'
195
195
196 assert_response :success
196 assert_response :success
197 assert_template 'edit'
197 assert_template 'edit'
198
198
199 assert_tag 'textarea',
199 assert_tag 'textarea',
200 :attributes => { :name => 'content[text]' },
200 :attributes => { :name => 'content[text]' },
201 :content => WikiPage.find_by_title('Another_page').content.text
201 :content => WikiPage.find_by_title('Another_page').content.text
202 end
202 end
203
203
204 def test_edit_section
204 def test_edit_section
205 @request.session[:user_id] = 2
205 @request.session[:user_id] = 2
206 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 2
206 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 2
207
207
208 assert_response :success
208 assert_response :success
209 assert_template 'edit'
209 assert_template 'edit'
210
210
211 page = WikiPage.find_by_title('Page_with_sections')
211 page = WikiPage.find_by_title('Page_with_sections')
212 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
212 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
213
213
214 assert_tag 'textarea',
214 assert_tag 'textarea',
215 :attributes => { :name => 'content[text]' },
215 :attributes => { :name => 'content[text]' },
216 :content => section
216 :content => section
217 assert_tag 'input',
217 assert_tag 'input',
218 :attributes => { :name => 'section', :type => 'hidden', :value => '2' }
218 :attributes => { :name => 'section', :type => 'hidden', :value => '2' }
219 assert_tag 'input',
219 assert_tag 'input',
220 :attributes => { :name => 'section_hash', :type => 'hidden', :value => hash }
220 :attributes => { :name => 'section_hash', :type => 'hidden', :value => hash }
221 end
221 end
222
222
223 def test_edit_invalid_section_should_respond_with_404
223 def test_edit_invalid_section_should_respond_with_404
224 @request.session[:user_id] = 2
224 @request.session[:user_id] = 2
225 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 10
225 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 10
226
226
227 assert_response 404
227 assert_response 404
228 end
228 end
229
229
230 def test_update_page
230 def test_update_page
231 @request.session[:user_id] = 2
231 @request.session[:user_id] = 2
232 assert_no_difference 'WikiPage.count' do
232 assert_no_difference 'WikiPage.count' do
233 assert_no_difference 'WikiContent.count' do
233 assert_no_difference 'WikiContent.count' do
234 assert_difference 'WikiContent::Version.count' do
234 assert_difference 'WikiContent::Version.count' do
235 put :update, :project_id => 1,
235 put :update, :project_id => 1,
236 :id => 'Another_page',
236 :id => 'Another_page',
237 :content => {
237 :content => {
238 :comments => "my comments",
238 :comments => "my comments",
239 :text => "edited",
239 :text => "edited",
240 :version => 1
240 :version => 1
241 }
241 }
242 end
242 end
243 end
243 end
244 end
244 end
245 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
245 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
246
246
247 page = Wiki.find(1).pages.find_by_title('Another_page')
247 page = Wiki.find(1).pages.find_by_title('Another_page')
248 assert_equal "edited", page.content.text
248 assert_equal "edited", page.content.text
249 assert_equal 2, page.content.version
249 assert_equal 2, page.content.version
250 assert_equal "my comments", page.content.comments
250 assert_equal "my comments", page.content.comments
251 end
251 end
252
252
253 def test_update_page_with_failure
253 def test_update_page_with_failure
254 @request.session[:user_id] = 2
254 @request.session[:user_id] = 2
255 assert_no_difference 'WikiPage.count' do
255 assert_no_difference 'WikiPage.count' do
256 assert_no_difference 'WikiContent.count' do
256 assert_no_difference 'WikiContent.count' do
257 assert_no_difference 'WikiContent::Version.count' do
257 assert_no_difference 'WikiContent::Version.count' do
258 put :update, :project_id => 1,
258 put :update, :project_id => 1,
259 :id => 'Another_page',
259 :id => 'Another_page',
260 :content => {
260 :content => {
261 :comments => 'a' * 300, # failure here, comment is too long
261 :comments => 'a' * 300, # failure here, comment is too long
262 :text => 'edited',
262 :text => 'edited',
263 :version => 1
263 :version => 1
264 }
264 }
265 end
265 end
266 end
266 end
267 end
267 end
268 assert_response :success
268 assert_response :success
269 assert_template 'edit'
269 assert_template 'edit'
270
270
271 assert_error_tag :descendant => {:content => /Comment is too long/}
271 assert_error_tag :descendant => {:content => /Comment is too long/}
272 assert_tag :tag => 'textarea', :attributes => {:id => 'content_text'}, :content => 'edited'
272 assert_tag :tag => 'textarea', :attributes => {:id => 'content_text'}, :content => 'edited'
273 assert_tag :tag => 'input', :attributes => {:id => 'content_version', :value => '1'}
273 assert_tag :tag => 'input', :attributes => {:id => 'content_version', :value => '1'}
274 end
274 end
275
275
276 def test_update_page_with_attachments_only_should_not_create_content_version
276 def test_update_page_with_attachments_only_should_not_create_content_version
277 @request.session[:user_id] = 2
277 @request.session[:user_id] = 2
278 assert_no_difference 'WikiPage.count' do
278 assert_no_difference 'WikiPage.count' do
279 assert_no_difference 'WikiContent.count' do
279 assert_no_difference 'WikiContent.count' do
280 assert_no_difference 'WikiContent::Version.count' do
280 assert_no_difference 'WikiContent::Version.count' do
281 assert_difference 'Attachment.count' do
281 assert_difference 'Attachment.count' do
282 put :update, :project_id => 1,
282 put :update, :project_id => 1,
283 :id => 'Another_page',
283 :id => 'Another_page',
284 :content => {
284 :content => {
285 :comments => '',
285 :comments => '',
286 :text => Wiki.find(1).find_page('Another_page').content.text,
286 :text => Wiki.find(1).find_page('Another_page').content.text,
287 :version => 1
287 :version => 1
288 },
288 },
289 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
289 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
290 end
290 end
291 end
291 end
292 end
292 end
293 end
293 end
294 end
294 end
295
295
296 def test_update_stale_page_should_not_raise_an_error
296 def test_update_stale_page_should_not_raise_an_error
297 @request.session[:user_id] = 2
297 @request.session[:user_id] = 2
298 c = Wiki.find(1).find_page('Another_page').content
298 c = Wiki.find(1).find_page('Another_page').content
299 c.text = 'Previous text'
299 c.text = 'Previous text'
300 c.save!
300 c.save!
301 assert_equal 2, c.version
301 assert_equal 2, c.version
302
302
303 assert_no_difference 'WikiPage.count' do
303 assert_no_difference 'WikiPage.count' do
304 assert_no_difference 'WikiContent.count' do
304 assert_no_difference 'WikiContent.count' do
305 assert_no_difference 'WikiContent::Version.count' do
305 assert_no_difference 'WikiContent::Version.count' do
306 put :update, :project_id => 1,
306 put :update, :project_id => 1,
307 :id => 'Another_page',
307 :id => 'Another_page',
308 :content => {
308 :content => {
309 :comments => 'My comments',
309 :comments => 'My comments',
310 :text => 'Text should not be lost',
310 :text => 'Text should not be lost',
311 :version => 1
311 :version => 1
312 }
312 }
313 end
313 end
314 end
314 end
315 end
315 end
316 assert_response :success
316 assert_response :success
317 assert_template 'edit'
317 assert_template 'edit'
318 assert_tag :div,
318 assert_tag :div,
319 :attributes => { :class => /error/ },
319 :attributes => { :class => /error/ },
320 :content => /Data has been updated by another user/
320 :content => /Data has been updated by another user/
321 assert_tag 'textarea',
321 assert_tag 'textarea',
322 :attributes => { :name => 'content[text]' },
322 :attributes => { :name => 'content[text]' },
323 :content => /Text should not be lost/
323 :content => /Text should not be lost/
324 assert_tag 'input',
324 assert_tag 'input',
325 :attributes => { :name => 'content[comments]', :value => 'My comments' }
325 :attributes => { :name => 'content[comments]', :value => 'My comments' }
326
326
327 c.reload
327 c.reload
328 assert_equal 'Previous text', c.text
328 assert_equal 'Previous text', c.text
329 assert_equal 2, c.version
329 assert_equal 2, c.version
330 end
330 end
331
331
332 def test_update_section
332 def test_update_section
333 @request.session[:user_id] = 2
333 @request.session[:user_id] = 2
334 page = WikiPage.find_by_title('Page_with_sections')
334 page = WikiPage.find_by_title('Page_with_sections')
335 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
335 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
336 text = page.content.text
336 text = page.content.text
337
337
338 assert_no_difference 'WikiPage.count' do
338 assert_no_difference 'WikiPage.count' do
339 assert_no_difference 'WikiContent.count' do
339 assert_no_difference 'WikiContent.count' do
340 assert_difference 'WikiContent::Version.count' do
340 assert_difference 'WikiContent::Version.count' do
341 put :update, :project_id => 1, :id => 'Page_with_sections',
341 put :update, :project_id => 1, :id => 'Page_with_sections',
342 :content => {
342 :content => {
343 :text => "New section content",
343 :text => "New section content",
344 :version => 3
344 :version => 3
345 },
345 },
346 :section => 2,
346 :section => 2,
347 :section_hash => hash
347 :section_hash => hash
348 end
348 end
349 end
349 end
350 end
350 end
351 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
351 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
352 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.reload.content.text
352 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.reload.content.text
353 end
353 end
354
354
355 def test_update_section_should_allow_stale_page_update
355 def test_update_section_should_allow_stale_page_update
356 @request.session[:user_id] = 2
356 @request.session[:user_id] = 2
357 page = WikiPage.find_by_title('Page_with_sections')
357 page = WikiPage.find_by_title('Page_with_sections')
358 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
358 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
359 text = page.content.text
359 text = page.content.text
360
360
361 assert_no_difference 'WikiPage.count' do
361 assert_no_difference 'WikiPage.count' do
362 assert_no_difference 'WikiContent.count' do
362 assert_no_difference 'WikiContent.count' do
363 assert_difference 'WikiContent::Version.count' do
363 assert_difference 'WikiContent::Version.count' do
364 put :update, :project_id => 1, :id => 'Page_with_sections',
364 put :update, :project_id => 1, :id => 'Page_with_sections',
365 :content => {
365 :content => {
366 :text => "New section content",
366 :text => "New section content",
367 :version => 2 # Current version is 3
367 :version => 2 # Current version is 3
368 },
368 },
369 :section => 2,
369 :section => 2,
370 :section_hash => hash
370 :section_hash => hash
371 end
371 end
372 end
372 end
373 end
373 end
374 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
374 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
375 page.reload
375 page.reload
376 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.content.text
376 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.content.text
377 assert_equal 4, page.content.version
377 assert_equal 4, page.content.version
378 end
378 end
379
379
380 def test_update_section_should_not_allow_stale_section_update
380 def test_update_section_should_not_allow_stale_section_update
381 @request.session[:user_id] = 2
381 @request.session[:user_id] = 2
382
382
383 assert_no_difference 'WikiPage.count' do
383 assert_no_difference 'WikiPage.count' do
384 assert_no_difference 'WikiContent.count' do
384 assert_no_difference 'WikiContent.count' do
385 assert_no_difference 'WikiContent::Version.count' do
385 assert_no_difference 'WikiContent::Version.count' do
386 put :update, :project_id => 1, :id => 'Page_with_sections',
386 put :update, :project_id => 1, :id => 'Page_with_sections',
387 :content => {
387 :content => {
388 :comments => 'My comments',
388 :comments => 'My comments',
389 :text => "Text should not be lost",
389 :text => "Text should not be lost",
390 :version => 3
390 :version => 3
391 },
391 },
392 :section => 2,
392 :section => 2,
393 :section_hash => Digest::MD5.hexdigest("wrong hash")
393 :section_hash => Digest::MD5.hexdigest("wrong hash")
394 end
394 end
395 end
395 end
396 end
396 end
397 assert_response :success
397 assert_response :success
398 assert_template 'edit'
398 assert_template 'edit'
399 assert_tag :div,
399 assert_tag :div,
400 :attributes => { :class => /error/ },
400 :attributes => { :class => /error/ },
401 :content => /Data has been updated by another user/
401 :content => /Data has been updated by another user/
402 assert_tag 'textarea',
402 assert_tag 'textarea',
403 :attributes => { :name => 'content[text]' },
403 :attributes => { :name => 'content[text]' },
404 :content => /Text should not be lost/
404 :content => /Text should not be lost/
405 assert_tag 'input',
405 assert_tag 'input',
406 :attributes => { :name => 'content[comments]', :value => 'My comments' }
406 :attributes => { :name => 'content[comments]', :value => 'My comments' }
407 end
407 end
408
408
409 def test_preview
409 def test_preview
410 @request.session[:user_id] = 2
410 @request.session[:user_id] = 2
411 xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation',
411 xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation',
412 :content => { :comments => '',
412 :content => { :comments => '',
413 :text => 'this is a *previewed text*',
413 :text => 'this is a *previewed text*',
414 :version => 3 }
414 :version => 3 }
415 assert_response :success
415 assert_response :success
416 assert_template 'common/_preview'
416 assert_template 'common/_preview'
417 assert_tag :tag => 'strong', :content => /previewed text/
417 assert_tag :tag => 'strong', :content => /previewed text/
418 end
418 end
419
419
420 def test_preview_new_page
420 def test_preview_new_page
421 @request.session[:user_id] = 2
421 @request.session[:user_id] = 2
422 xhr :post, :preview, :project_id => 1, :id => 'New page',
422 xhr :post, :preview, :project_id => 1, :id => 'New page',
423 :content => { :text => 'h1. New page',
423 :content => { :text => 'h1. New page',
424 :comments => '',
424 :comments => '',
425 :version => 0 }
425 :version => 0 }
426 assert_response :success
426 assert_response :success
427 assert_template 'common/_preview'
427 assert_template 'common/_preview'
428 assert_tag :tag => 'h1', :content => /New page/
428 assert_tag :tag => 'h1', :content => /New page/
429 end
429 end
430
430
431 def test_history
431 def test_history
432 get :history, :project_id => 1, :id => 'CookBook_documentation'
432 get :history, :project_id => 1, :id => 'CookBook_documentation'
433 assert_response :success
433 assert_response :success
434 assert_template 'history'
434 assert_template 'history'
435 assert_not_nil assigns(:versions)
435 assert_not_nil assigns(:versions)
436 assert_equal 3, assigns(:versions).size
436 assert_equal 3, assigns(:versions).size
437 assert_select "input[type=submit][name=commit]"
437 assert_select "input[type=submit][name=commit]"
438 end
438 end
439
439
440 def test_history_with_one_version
440 def test_history_with_one_version
441 get :history, :project_id => 1, :id => 'Another_page'
441 get :history, :project_id => 1, :id => 'Another_page'
442 assert_response :success
442 assert_response :success
443 assert_template 'history'
443 assert_template 'history'
444 assert_not_nil assigns(:versions)
444 assert_not_nil assigns(:versions)
445 assert_equal 1, assigns(:versions).size
445 assert_equal 1, assigns(:versions).size
446 assert_select "input[type=submit][name=commit]", false
446 assert_select "input[type=submit][name=commit]", false
447 end
447 end
448
448
449 def test_diff
449 def test_diff
450 get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => 2, :version_from => 1
450 get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => 2, :version_from => 1
451 assert_response :success
451 assert_response :success
452 assert_template 'diff'
452 assert_template 'diff'
453 assert_tag :tag => 'span', :attributes => { :class => 'diff_in'},
453 assert_tag :tag => 'span', :attributes => { :class => 'diff_in'},
454 :content => /updated/
454 :content => /updated/
455 end
455 end
456
456
457 def test_annotate
457 def test_annotate
458 get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => 2
458 get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => 2
459 assert_response :success
459 assert_response :success
460 assert_template 'annotate'
460 assert_template 'annotate'
461
461
462 # Line 1
462 # Line 1
463 assert_tag :tag => 'tr', :child => {
463 assert_tag :tag => 'tr', :child => {
464 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '1', :sibling => {
464 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '1', :sibling => {
465 :tag => 'td', :attributes => {:class => 'author'}, :content => /John Smith/, :sibling => {
465 :tag => 'td', :attributes => {:class => 'author'}, :content => /John Smith/, :sibling => {
466 :tag => 'td', :content => /h1\. CookBook documentation/
466 :tag => 'td', :content => /h1\. CookBook documentation/
467 }
467 }
468 }
468 }
469 }
469 }
470
470
471 # Line 5
471 # Line 5
472 assert_tag :tag => 'tr', :child => {
472 assert_tag :tag => 'tr', :child => {
473 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '5', :sibling => {
473 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '5', :sibling => {
474 :tag => 'td', :attributes => {:class => 'author'}, :content => /redMine Admin/, :sibling => {
474 :tag => 'td', :attributes => {:class => 'author'}, :content => /redMine Admin/, :sibling => {
475 :tag => 'td', :content => /Some updated \[\[documentation\]\] here/
475 :tag => 'td', :content => /Some updated \[\[documentation\]\] here/
476 }
476 }
477 }
477 }
478 }
478 }
479 end
479 end
480
480
481 def test_get_rename
481 def test_get_rename
482 @request.session[:user_id] = 2
482 @request.session[:user_id] = 2
483 get :rename, :project_id => 1, :id => 'Another_page'
483 get :rename, :project_id => 1, :id => 'Another_page'
484 assert_response :success
484 assert_response :success
485 assert_template 'rename'
485 assert_template 'rename'
486 assert_tag 'option',
486 assert_tag 'option',
487 :attributes => {:value => ''},
487 :attributes => {:value => ''},
488 :content => '',
488 :content => '',
489 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
489 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
490 assert_no_tag 'option',
490 assert_no_tag 'option',
491 :attributes => {:selected => 'selected'},
491 :attributes => {:selected => 'selected'},
492 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
492 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
493 end
493 end
494
494
495 def test_get_rename_child_page
495 def test_get_rename_child_page
496 @request.session[:user_id] = 2
496 @request.session[:user_id] = 2
497 get :rename, :project_id => 1, :id => 'Child_1'
497 get :rename, :project_id => 1, :id => 'Child_1'
498 assert_response :success
498 assert_response :success
499 assert_template 'rename'
499 assert_template 'rename'
500 assert_tag 'option',
500 assert_tag 'option',
501 :attributes => {:value => ''},
501 :attributes => {:value => ''},
502 :content => '',
502 :content => '',
503 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
503 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
504 assert_tag 'option',
504 assert_tag 'option',
505 :attributes => {:value => '2', :selected => 'selected'},
505 :attributes => {:value => '2', :selected => 'selected'},
506 :content => /Another page/,
506 :content => /Another page/,
507 :parent => {
507 :parent => {
508 :tag => 'select',
508 :tag => 'select',
509 :attributes => {:name => 'wiki_page[parent_id]'}
509 :attributes => {:name => 'wiki_page[parent_id]'}
510 }
510 }
511 end
511 end
512
512
513 def test_rename_with_redirect
513 def test_rename_with_redirect
514 @request.session[:user_id] = 2
514 @request.session[:user_id] = 2
515 post :rename, :project_id => 1, :id => 'Another_page',
515 post :rename, :project_id => 1, :id => 'Another_page',
516 :wiki_page => { :title => 'Another renamed page',
516 :wiki_page => { :title => 'Another renamed page',
517 :redirect_existing_links => 1 }
517 :redirect_existing_links => 1 }
518 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
518 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
519 wiki = Project.find(1).wiki
519 wiki = Project.find(1).wiki
520 # Check redirects
520 # Check redirects
521 assert_not_nil wiki.find_page('Another page')
521 assert_not_nil wiki.find_page('Another page')
522 assert_nil wiki.find_page('Another page', :with_redirect => false)
522 assert_nil wiki.find_page('Another page', :with_redirect => false)
523 end
523 end
524
524
525 def test_rename_without_redirect
525 def test_rename_without_redirect
526 @request.session[:user_id] = 2
526 @request.session[:user_id] = 2
527 post :rename, :project_id => 1, :id => 'Another_page',
527 post :rename, :project_id => 1, :id => 'Another_page',
528 :wiki_page => { :title => 'Another renamed page',
528 :wiki_page => { :title => 'Another renamed page',
529 :redirect_existing_links => "0" }
529 :redirect_existing_links => "0" }
530 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
530 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
531 wiki = Project.find(1).wiki
531 wiki = Project.find(1).wiki
532 # Check that there's no redirects
532 # Check that there's no redirects
533 assert_nil wiki.find_page('Another page')
533 assert_nil wiki.find_page('Another page')
534 end
534 end
535
535
536 def test_rename_with_parent_assignment
536 def test_rename_with_parent_assignment
537 @request.session[:user_id] = 2
537 @request.session[:user_id] = 2
538 post :rename, :project_id => 1, :id => 'Another_page',
538 post :rename, :project_id => 1, :id => 'Another_page',
539 :wiki_page => { :title => 'Another page', :redirect_existing_links => "0", :parent_id => '4' }
539 :wiki_page => { :title => 'Another page', :redirect_existing_links => "0", :parent_id => '4' }
540 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
540 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
541 assert_equal WikiPage.find(4), WikiPage.find_by_title('Another_page').parent
541 assert_equal WikiPage.find(4), WikiPage.find_by_title('Another_page').parent
542 end
542 end
543
543
544 def test_rename_with_parent_unassignment
544 def test_rename_with_parent_unassignment
545 @request.session[:user_id] = 2
545 @request.session[:user_id] = 2
546 post :rename, :project_id => 1, :id => 'Child_1',
546 post :rename, :project_id => 1, :id => 'Child_1',
547 :wiki_page => { :title => 'Child 1', :redirect_existing_links => "0", :parent_id => '' }
547 :wiki_page => { :title => 'Child 1', :redirect_existing_links => "0", :parent_id => '' }
548 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Child_1'
548 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Child_1'
549 assert_nil WikiPage.find_by_title('Child_1').parent
549 assert_nil WikiPage.find_by_title('Child_1').parent
550 end
550 end
551
551
552 def test_destroy_child
552 def test_destroy_child
553 @request.session[:user_id] = 2
553 @request.session[:user_id] = 2
554 delete :destroy, :project_id => 1, :id => 'Child_1'
554 delete :destroy, :project_id => 1, :id => 'Child_1'
555 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
555 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
556 end
556 end
557
557
558 def test_destroy_parent
558 def test_destroy_parent
559 @request.session[:user_id] = 2
559 @request.session[:user_id] = 2
560 assert_no_difference('WikiPage.count') do
560 assert_no_difference('WikiPage.count') do
561 delete :destroy, :project_id => 1, :id => 'Another_page'
561 delete :destroy, :project_id => 1, :id => 'Another_page'
562 end
562 end
563 assert_response :success
563 assert_response :success
564 assert_template 'destroy'
564 assert_template 'destroy'
565 end
565 end
566
566
567 def test_destroy_parent_with_nullify
567 def test_destroy_parent_with_nullify
568 @request.session[:user_id] = 2
568 @request.session[:user_id] = 2
569 assert_difference('WikiPage.count', -1) do
569 assert_difference('WikiPage.count', -1) do
570 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'nullify'
570 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'nullify'
571 end
571 end
572 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
572 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
573 assert_nil WikiPage.find_by_id(2)
573 assert_nil WikiPage.find_by_id(2)
574 end
574 end
575
575
576 def test_destroy_parent_with_cascade
576 def test_destroy_parent_with_cascade
577 @request.session[:user_id] = 2
577 @request.session[:user_id] = 2
578 assert_difference('WikiPage.count', -3) do
578 assert_difference('WikiPage.count', -3) do
579 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'destroy'
579 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'destroy'
580 end
580 end
581 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
581 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
582 assert_nil WikiPage.find_by_id(2)
582 assert_nil WikiPage.find_by_id(2)
583 assert_nil WikiPage.find_by_id(5)
583 assert_nil WikiPage.find_by_id(5)
584 end
584 end
585
585
586 def test_destroy_parent_with_reassign
586 def test_destroy_parent_with_reassign
587 @request.session[:user_id] = 2
587 @request.session[:user_id] = 2
588 assert_difference('WikiPage.count', -1) do
588 assert_difference('WikiPage.count', -1) do
589 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'reassign', :reassign_to_id => 1
589 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'reassign', :reassign_to_id => 1
590 end
590 end
591 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
591 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
592 assert_nil WikiPage.find_by_id(2)
592 assert_nil WikiPage.find_by_id(2)
593 assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent
593 assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent
594 end
594 end
595
595
596 def test_index
596 def test_index
597 get :index, :project_id => 'ecookbook'
597 get :index, :project_id => 'ecookbook'
598 assert_response :success
598 assert_response :success
599 assert_template 'index'
599 assert_template 'index'
600 pages = assigns(:pages)
600 pages = assigns(:pages)
601 assert_not_nil pages
601 assert_not_nil pages
602 assert_equal Project.find(1).wiki.pages.size, pages.size
602 assert_equal Project.find(1).wiki.pages.size, pages.size
603 assert_equal pages.first.content.updated_on, pages.first.updated_on
603 assert_equal pages.first.content.updated_on, pages.first.updated_on
604
604
605 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
605 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
606 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/CookBook_documentation' },
606 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/CookBook_documentation' },
607 :content => 'CookBook documentation' },
607 :content => 'CookBook documentation' },
608 :child => { :tag => 'ul',
608 :child => { :tag => 'ul',
609 :child => { :tag => 'li',
609 :child => { :tag => 'li',
610 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
610 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
611 :content => 'Page with an inline image' } } } },
611 :content => 'Page with an inline image' } } } },
612 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Another_page' },
612 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Another_page' },
613 :content => 'Another page' } }
613 :content => 'Another page' } }
614 end
614 end
615
615
616 def test_index_should_include_atom_link
616 def test_index_should_include_atom_link
617 get :index, :project_id => 'ecookbook'
617 get :index, :project_id => 'ecookbook'
618 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
618 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
619 end
619 end
620
620
621 def test_export
621 def test_export_to_html
622 @request.session[:user_id] = 2
622 @request.session[:user_id] = 2
623 get :export, :project_id => 'ecookbook'
623 get :export, :project_id => 'ecookbook'
624
624
625 assert_response :success
625 assert_response :success
626 assert_not_nil assigns(:pages)
626 assert_not_nil assigns(:pages)
627 assert assigns(:pages).any?
627 assert_equal "text/html", @response.content_type
628 assert_equal "text/html", @response.content_type
628
629
629 assert_select "a[name=?]", "CookBook_documentation"
630 assert_select "a[name=?]", "CookBook_documentation"
630 assert_select "a[name=?]", "Another_page"
631 assert_select "a[name=?]", "Another_page"
631 assert_select "a[name=?]", "Page_with_an_inline_image"
632 assert_select "a[name=?]", "Page_with_an_inline_image"
632 end
633 end
633
634
634 def test_export_without_permission
635 def test_export_to_pdf
636 @request.session[:user_id] = 2
637 get :export, :project_id => 'ecookbook', :format => 'pdf'
638
639 assert_response :success
640 assert_not_nil assigns(:pages)
641 assert assigns(:pages).any?
642 assert_equal 'application/pdf', @response.content_type
643 assert_equal 'attachment; filename="ecookbook.pdf"', @response.headers['Content-Disposition']
644 assert @response.body.starts_with?('%PDF')
645 end
646
647 def test_export_without_permission_should_redirect
635 get :export, :project_id => 'ecookbook'
648 get :export, :project_id => 'ecookbook'
636
649
637 assert_response 302
650 assert_response 302
638 end
651 end
639
652
640 def test_date_index
653 def test_date_index
641 get :date_index, :project_id => 'ecookbook'
654 get :date_index, :project_id => 'ecookbook'
642
655
643 assert_response :success
656 assert_response :success
644 assert_template 'date_index'
657 assert_template 'date_index'
645 assert_not_nil assigns(:pages)
658 assert_not_nil assigns(:pages)
646 assert_not_nil assigns(:pages_by_date)
659 assert_not_nil assigns(:pages_by_date)
647
660
648 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
661 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
649 end
662 end
650
663
651 def test_not_found
664 def test_not_found
652 get :show, :project_id => 999
665 get :show, :project_id => 999
653 assert_response 404
666 assert_response 404
654 end
667 end
655
668
656 def test_protect_page
669 def test_protect_page
657 page = WikiPage.find_by_wiki_id_and_title(1, 'Another_page')
670 page = WikiPage.find_by_wiki_id_and_title(1, 'Another_page')
658 assert !page.protected?
671 assert !page.protected?
659 @request.session[:user_id] = 2
672 @request.session[:user_id] = 2
660 post :protect, :project_id => 1, :id => page.title, :protected => '1'
673 post :protect, :project_id => 1, :id => page.title, :protected => '1'
661 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
674 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
662 assert page.reload.protected?
675 assert page.reload.protected?
663 end
676 end
664
677
665 def test_unprotect_page
678 def test_unprotect_page
666 page = WikiPage.find_by_wiki_id_and_title(1, 'CookBook_documentation')
679 page = WikiPage.find_by_wiki_id_and_title(1, 'CookBook_documentation')
667 assert page.protected?
680 assert page.protected?
668 @request.session[:user_id] = 2
681 @request.session[:user_id] = 2
669 post :protect, :project_id => 1, :id => page.title, :protected => '0'
682 post :protect, :project_id => 1, :id => page.title, :protected => '0'
670 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'CookBook_documentation'
683 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'CookBook_documentation'
671 assert !page.reload.protected?
684 assert !page.reload.protected?
672 end
685 end
673
686
674 def test_show_page_with_edit_link
687 def test_show_page_with_edit_link
675 @request.session[:user_id] = 2
688 @request.session[:user_id] = 2
676 get :show, :project_id => 1
689 get :show, :project_id => 1
677 assert_response :success
690 assert_response :success
678 assert_template 'show'
691 assert_template 'show'
679 assert_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
692 assert_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
680 end
693 end
681
694
682 def test_show_page_without_edit_link
695 def test_show_page_without_edit_link
683 @request.session[:user_id] = 4
696 @request.session[:user_id] = 4
684 get :show, :project_id => 1
697 get :show, :project_id => 1
685 assert_response :success
698 assert_response :success
686 assert_template 'show'
699 assert_template 'show'
687 assert_no_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
700 assert_no_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
688 end
701 end
689
702
690 def test_show_pdf
703 def test_show_pdf
691 @request.session[:user_id] = 2
704 @request.session[:user_id] = 2
692 get :show, :project_id => 1, :format => 'pdf'
705 get :show, :project_id => 1, :format => 'pdf'
693 assert_response :success
706 assert_response :success
694 assert_not_nil assigns(:page)
707 assert_not_nil assigns(:page)
695 assert_equal 'application/pdf', @response.content_type
708 assert_equal 'application/pdf', @response.content_type
696 assert_equal 'attachment; filename="CookBook_documentation.pdf"',
709 assert_equal 'attachment; filename="CookBook_documentation.pdf"',
697 @response.headers['Content-Disposition']
710 @response.headers['Content-Disposition']
698 end
711 end
699
712
700 def test_show_html
713 def test_show_html
701 @request.session[:user_id] = 2
714 @request.session[:user_id] = 2
702 get :show, :project_id => 1, :format => 'html'
715 get :show, :project_id => 1, :format => 'html'
703 assert_response :success
716 assert_response :success
704 assert_not_nil assigns(:page)
717 assert_not_nil assigns(:page)
705 assert_equal 'text/html', @response.content_type
718 assert_equal 'text/html', @response.content_type
706 assert_equal 'attachment; filename="CookBook_documentation.html"',
719 assert_equal 'attachment; filename="CookBook_documentation.html"',
707 @response.headers['Content-Disposition']
720 @response.headers['Content-Disposition']
708 end
721 end
709
722
710 def test_show_txt
723 def test_show_txt
711 @request.session[:user_id] = 2
724 @request.session[:user_id] = 2
712 get :show, :project_id => 1, :format => 'txt'
725 get :show, :project_id => 1, :format => 'txt'
713 assert_response :success
726 assert_response :success
714 assert_not_nil assigns(:page)
727 assert_not_nil assigns(:page)
715 assert_equal 'text/plain', @response.content_type
728 assert_equal 'text/plain', @response.content_type
716 assert_equal 'attachment; filename="CookBook_documentation.txt"',
729 assert_equal 'attachment; filename="CookBook_documentation.txt"',
717 @response.headers['Content-Disposition']
730 @response.headers['Content-Disposition']
718 end
731 end
719
732
720 def test_edit_unprotected_page
733 def test_edit_unprotected_page
721 # Non members can edit unprotected wiki pages
734 # Non members can edit unprotected wiki pages
722 @request.session[:user_id] = 4
735 @request.session[:user_id] = 4
723 get :edit, :project_id => 1, :id => 'Another_page'
736 get :edit, :project_id => 1, :id => 'Another_page'
724 assert_response :success
737 assert_response :success
725 assert_template 'edit'
738 assert_template 'edit'
726 end
739 end
727
740
728 def test_edit_protected_page_by_nonmember
741 def test_edit_protected_page_by_nonmember
729 # Non members can't edit protected wiki pages
742 # Non members can't edit protected wiki pages
730 @request.session[:user_id] = 4
743 @request.session[:user_id] = 4
731 get :edit, :project_id => 1, :id => 'CookBook_documentation'
744 get :edit, :project_id => 1, :id => 'CookBook_documentation'
732 assert_response 403
745 assert_response 403
733 end
746 end
734
747
735 def test_edit_protected_page_by_member
748 def test_edit_protected_page_by_member
736 @request.session[:user_id] = 2
749 @request.session[:user_id] = 2
737 get :edit, :project_id => 1, :id => 'CookBook_documentation'
750 get :edit, :project_id => 1, :id => 'CookBook_documentation'
738 assert_response :success
751 assert_response :success
739 assert_template 'edit'
752 assert_template 'edit'
740 end
753 end
741
754
742 def test_history_of_non_existing_page_should_return_404
755 def test_history_of_non_existing_page_should_return_404
743 get :history, :project_id => 1, :id => 'Unknown_page'
756 get :history, :project_id => 1, :id => 'Unknown_page'
744 assert_response 404
757 assert_response 404
745 end
758 end
746
759
747 def test_add_attachment
760 def test_add_attachment
748 @request.session[:user_id] = 2
761 @request.session[:user_id] = 2
749 assert_difference 'Attachment.count' do
762 assert_difference 'Attachment.count' do
750 post :add_attachment, :project_id => 1, :id => 'CookBook_documentation',
763 post :add_attachment, :project_id => 1, :id => 'CookBook_documentation',
751 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
764 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
752 end
765 end
753 attachment = Attachment.first(:order => 'id DESC')
766 attachment = Attachment.first(:order => 'id DESC')
754 assert_equal Wiki.find(1).find_page('CookBook_documentation'), attachment.container
767 assert_equal Wiki.find(1).find_page('CookBook_documentation'), attachment.container
755 end
768 end
756 end
769 end
@@ -1,115 +1,124
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class RoutingWikiTest < ActionController::IntegrationTest
20 class RoutingWikiTest < ActionController::IntegrationTest
21 def test_wiki_matching
21 def test_wiki_matching
22 assert_routing(
22 assert_routing(
23 { :method => 'get', :path => "/projects/567/wiki" },
23 { :method => 'get', :path => "/projects/567/wiki" },
24 { :controller => 'wiki', :action => 'show', :project_id => '567' }
24 { :controller => 'wiki', :action => 'show', :project_id => '567' }
25 )
25 )
26 assert_routing(
26 assert_routing(
27 { :method => 'get', :path => "/projects/567/wiki/lalala" },
27 { :method => 'get', :path => "/projects/567/wiki/lalala" },
28 { :controller => 'wiki', :action => 'show', :project_id => '567',
28 { :controller => 'wiki', :action => 'show', :project_id => '567',
29 :id => 'lalala' }
29 :id => 'lalala' }
30 )
30 )
31 assert_routing(
31 assert_routing(
32 { :method => 'get', :path => "/projects/567/wiki/lalala.pdf" },
33 { :controller => 'wiki', :action => 'show', :project_id => '567',
34 :id => 'lalala', :format => 'pdf' }
35 )
36 assert_routing(
32 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff" },
37 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff" },
33 { :controller => 'wiki', :action => 'diff', :project_id => '1',
38 { :controller => 'wiki', :action => 'diff', :project_id => '1',
34 :id => 'CookBook_documentation' }
39 :id => 'CookBook_documentation' }
35 )
40 )
36 assert_routing(
41 assert_routing(
37 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff/2" },
42 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff/2" },
38 { :controller => 'wiki', :action => 'diff', :project_id => '1',
43 { :controller => 'wiki', :action => 'diff', :project_id => '1',
39 :id => 'CookBook_documentation', :version => '2' }
44 :id => 'CookBook_documentation', :version => '2' }
40 )
45 )
41 assert_routing(
46 assert_routing(
42 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff/2/vs/1" },
47 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff/2/vs/1" },
43 { :controller => 'wiki', :action => 'diff', :project_id => '1',
48 { :controller => 'wiki', :action => 'diff', :project_id => '1',
44 :id => 'CookBook_documentation', :version => '2', :version_from => '1' }
49 :id => 'CookBook_documentation', :version => '2', :version_from => '1' }
45 )
50 )
46 assert_routing(
51 assert_routing(
47 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/annotate/2" },
52 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/annotate/2" },
48 { :controller => 'wiki', :action => 'annotate', :project_id => '1',
53 { :controller => 'wiki', :action => 'annotate', :project_id => '1',
49 :id => 'CookBook_documentation', :version => '2' }
54 :id => 'CookBook_documentation', :version => '2' }
50 )
55 )
51 end
56 end
52
57
53 def test_wiki_misc
58 def test_wiki_misc
54 assert_routing(
59 assert_routing(
55 { :method => 'get', :path => "/projects/567/wiki/date_index" },
60 { :method => 'get', :path => "/projects/567/wiki/date_index" },
56 { :controller => 'wiki', :action => 'date_index', :project_id => '567' }
61 { :controller => 'wiki', :action => 'date_index', :project_id => '567' }
57 )
62 )
58 assert_routing(
63 assert_routing(
59 { :method => 'get', :path => "/projects/567/wiki/export" },
64 { :method => 'get', :path => "/projects/567/wiki/export" },
60 { :controller => 'wiki', :action => 'export', :project_id => '567' }
65 { :controller => 'wiki', :action => 'export', :project_id => '567' }
61 )
66 )
62 assert_routing(
67 assert_routing(
68 { :method => 'get', :path => "/projects/567/wiki/export.pdf" },
69 { :controller => 'wiki', :action => 'export', :project_id => '567', :format => 'pdf' }
70 )
71 assert_routing(
63 { :method => 'get', :path => "/projects/567/wiki/index" },
72 { :method => 'get', :path => "/projects/567/wiki/index" },
64 { :controller => 'wiki', :action => 'index', :project_id => '567' }
73 { :controller => 'wiki', :action => 'index', :project_id => '567' }
65 )
74 )
66 end
75 end
67
76
68 def test_wiki_resources
77 def test_wiki_resources
69 assert_routing(
78 assert_routing(
70 { :method => 'get', :path => "/projects/567/wiki/my_page/edit" },
79 { :method => 'get', :path => "/projects/567/wiki/my_page/edit" },
71 { :controller => 'wiki', :action => 'edit', :project_id => '567',
80 { :controller => 'wiki', :action => 'edit', :project_id => '567',
72 :id => 'my_page' }
81 :id => 'my_page' }
73 )
82 )
74 assert_routing(
83 assert_routing(
75 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/history" },
84 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/history" },
76 { :controller => 'wiki', :action => 'history', :project_id => '1',
85 { :controller => 'wiki', :action => 'history', :project_id => '1',
77 :id => 'CookBook_documentation' }
86 :id => 'CookBook_documentation' }
78 )
87 )
79 assert_routing(
88 assert_routing(
80 { :method => 'get', :path => "/projects/22/wiki/ladida/rename" },
89 { :method => 'get', :path => "/projects/22/wiki/ladida/rename" },
81 { :controller => 'wiki', :action => 'rename', :project_id => '22',
90 { :controller => 'wiki', :action => 'rename', :project_id => '22',
82 :id => 'ladida' }
91 :id => 'ladida' }
83 )
92 )
84 assert_routing(
93 assert_routing(
85 { :method => 'post', :path => "/projects/567/wiki/CookBook_documentation/preview" },
94 { :method => 'post', :path => "/projects/567/wiki/CookBook_documentation/preview" },
86 { :controller => 'wiki', :action => 'preview', :project_id => '567',
95 { :controller => 'wiki', :action => 'preview', :project_id => '567',
87 :id => 'CookBook_documentation' }
96 :id => 'CookBook_documentation' }
88 )
97 )
89 assert_routing(
98 assert_routing(
90 { :method => 'post', :path => "/projects/22/wiki/ladida/rename" },
99 { :method => 'post', :path => "/projects/22/wiki/ladida/rename" },
91 { :controller => 'wiki', :action => 'rename', :project_id => '22',
100 { :controller => 'wiki', :action => 'rename', :project_id => '22',
92 :id => 'ladida' }
101 :id => 'ladida' }
93 )
102 )
94 assert_routing(
103 assert_routing(
95 { :method => 'post', :path => "/projects/22/wiki/ladida/protect" },
104 { :method => 'post', :path => "/projects/22/wiki/ladida/protect" },
96 { :controller => 'wiki', :action => 'protect', :project_id => '22',
105 { :controller => 'wiki', :action => 'protect', :project_id => '22',
97 :id => 'ladida' }
106 :id => 'ladida' }
98 )
107 )
99 assert_routing(
108 assert_routing(
100 { :method => 'post', :path => "/projects/22/wiki/ladida/add_attachment" },
109 { :method => 'post', :path => "/projects/22/wiki/ladida/add_attachment" },
101 { :controller => 'wiki', :action => 'add_attachment', :project_id => '22',
110 { :controller => 'wiki', :action => 'add_attachment', :project_id => '22',
102 :id => 'ladida' }
111 :id => 'ladida' }
103 )
112 )
104 assert_routing(
113 assert_routing(
105 { :method => 'put', :path => "/projects/567/wiki/my_page" },
114 { :method => 'put', :path => "/projects/567/wiki/my_page" },
106 { :controller => 'wiki', :action => 'update', :project_id => '567',
115 { :controller => 'wiki', :action => 'update', :project_id => '567',
107 :id => 'my_page' }
116 :id => 'my_page' }
108 )
117 )
109 assert_routing(
118 assert_routing(
110 { :method => 'delete', :path => "/projects/22/wiki/ladida" },
119 { :method => 'delete', :path => "/projects/22/wiki/ladida" },
111 { :controller => 'wiki', :action => 'destroy', :project_id => '22',
120 { :controller => 'wiki', :action => 'destroy', :project_id => '22',
112 :id => 'ladida' }
121 :id => 'ladida' }
113 )
122 )
114 end
123 end
115 end
124 end
General Comments 0
You need to be logged in to leave comments. Login now