##// END OF EJS Templates
Ability to watch a wiki or a single wiki page (#413)....
Jean-Philippe Lang -
r2666:85ce903cfa5f
parent child
Show More
@@ -1,233 +1,234
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 class WikiController < ApplicationController
20 class WikiController < ApplicationController
21 before_filter :find_wiki, :authorize
21 before_filter :find_wiki, :authorize
22 before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
22 before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
23
23
24 verify :method => :post, :only => [:destroy, :protect], :redirect_to => { :action => :index }
24 verify :method => :post, :only => [:destroy, :protect], :redirect_to => { :action => :index }
25
25
26 helper :attachments
26 helper :attachments
27 include AttachmentsHelper
27 include AttachmentsHelper
28 helper :watchers
28
29
29 # display a page (in editing mode if it doesn't exist)
30 # display a page (in editing mode if it doesn't exist)
30 def index
31 def index
31 page_title = params[:page]
32 page_title = params[:page]
32 @page = @wiki.find_or_new_page(page_title)
33 @page = @wiki.find_or_new_page(page_title)
33 if @page.new_record?
34 if @page.new_record?
34 if User.current.allowed_to?(:edit_wiki_pages, @project)
35 if User.current.allowed_to?(:edit_wiki_pages, @project)
35 edit
36 edit
36 render :action => 'edit'
37 render :action => 'edit'
37 else
38 else
38 render_404
39 render_404
39 end
40 end
40 return
41 return
41 end
42 end
42 if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
43 if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
43 # Redirects user to the current version if he's not allowed to view previous versions
44 # Redirects user to the current version if he's not allowed to view previous versions
44 redirect_to :version => nil
45 redirect_to :version => nil
45 return
46 return
46 end
47 end
47 @content = @page.content_for_version(params[:version])
48 @content = @page.content_for_version(params[:version])
48 if params[:format] == 'html'
49 if params[:format] == 'html'
49 export = render_to_string :action => 'export', :layout => false
50 export = render_to_string :action => 'export', :layout => false
50 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
51 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
51 return
52 return
52 elsif params[:format] == 'txt'
53 elsif params[:format] == 'txt'
53 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
54 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
54 return
55 return
55 end
56 end
56 @editable = editable?
57 @editable = editable?
57 render :action => 'show'
58 render :action => 'show'
58 end
59 end
59
60
60 # edit an existing page or a new one
61 # edit an existing page or a new one
61 def edit
62 def edit
62 @page = @wiki.find_or_new_page(params[:page])
63 @page = @wiki.find_or_new_page(params[:page])
63 return render_403 unless editable?
64 return render_403 unless editable?
64 @page.content = WikiContent.new(:page => @page) if @page.new_record?
65 @page.content = WikiContent.new(:page => @page) if @page.new_record?
65
66
66 @content = @page.content_for_version(params[:version])
67 @content = @page.content_for_version(params[:version])
67 @content.text = initial_page_content(@page) if @content.text.blank?
68 @content.text = initial_page_content(@page) if @content.text.blank?
68 # don't keep previous comment
69 # don't keep previous comment
69 @content.comments = nil
70 @content.comments = nil
70 if request.get?
71 if request.get?
71 # To prevent StaleObjectError exception when reverting to a previous version
72 # To prevent StaleObjectError exception when reverting to a previous version
72 @content.version = @page.content.version
73 @content.version = @page.content.version
73 else
74 else
74 if !@page.new_record? && @content.text == params[:content][:text]
75 if !@page.new_record? && @content.text == params[:content][:text]
75 # don't save if text wasn't changed
76 # don't save if text wasn't changed
76 redirect_to :action => 'index', :id => @project, :page => @page.title
77 redirect_to :action => 'index', :id => @project, :page => @page.title
77 return
78 return
78 end
79 end
79 #@content.text = params[:content][:text]
80 #@content.text = params[:content][:text]
80 #@content.comments = params[:content][:comments]
81 #@content.comments = params[:content][:comments]
81 @content.attributes = params[:content]
82 @content.attributes = params[:content]
82 @content.author = User.current
83 @content.author = User.current
83 # if page is new @page.save will also save content, but not if page isn't a new record
84 # if page is new @page.save will also save content, but not if page isn't a new record
84 if (@page.new_record? ? @page.save : @content.save)
85 if (@page.new_record? ? @page.save : @content.save)
85 redirect_to :action => 'index', :id => @project, :page => @page.title
86 redirect_to :action => 'index', :id => @project, :page => @page.title
86 end
87 end
87 end
88 end
88 rescue ActiveRecord::StaleObjectError
89 rescue ActiveRecord::StaleObjectError
89 # Optimistic locking exception
90 # Optimistic locking exception
90 flash[:error] = l(:notice_locking_conflict)
91 flash[:error] = l(:notice_locking_conflict)
91 end
92 end
92
93
93 # rename a page
94 # rename a page
94 def rename
95 def rename
95 return render_403 unless editable?
96 return render_403 unless editable?
96 @page.redirect_existing_links = true
97 @page.redirect_existing_links = true
97 # used to display the *original* title if some AR validation errors occur
98 # used to display the *original* title if some AR validation errors occur
98 @original_title = @page.pretty_title
99 @original_title = @page.pretty_title
99 if request.post? && @page.update_attributes(params[:wiki_page])
100 if request.post? && @page.update_attributes(params[:wiki_page])
100 flash[:notice] = l(:notice_successful_update)
101 flash[:notice] = l(:notice_successful_update)
101 redirect_to :action => 'index', :id => @project, :page => @page.title
102 redirect_to :action => 'index', :id => @project, :page => @page.title
102 end
103 end
103 end
104 end
104
105
105 def protect
106 def protect
106 @page.update_attribute :protected, params[:protected]
107 @page.update_attribute :protected, params[:protected]
107 redirect_to :action => 'index', :id => @project, :page => @page.title
108 redirect_to :action => 'index', :id => @project, :page => @page.title
108 end
109 end
109
110
110 # show page history
111 # show page history
111 def history
112 def history
112 @version_count = @page.content.versions.count
113 @version_count = @page.content.versions.count
113 @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
114 @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
114 # don't load text
115 # don't load text
115 @versions = @page.content.versions.find :all,
116 @versions = @page.content.versions.find :all,
116 :select => "id, author_id, comments, updated_on, version",
117 :select => "id, author_id, comments, updated_on, version",
117 :order => 'version DESC',
118 :order => 'version DESC',
118 :limit => @version_pages.items_per_page + 1,
119 :limit => @version_pages.items_per_page + 1,
119 :offset => @version_pages.current.offset
120 :offset => @version_pages.current.offset
120
121
121 render :layout => false if request.xhr?
122 render :layout => false if request.xhr?
122 end
123 end
123
124
124 def diff
125 def diff
125 @diff = @page.diff(params[:version], params[:version_from])
126 @diff = @page.diff(params[:version], params[:version_from])
126 render_404 unless @diff
127 render_404 unless @diff
127 end
128 end
128
129
129 def annotate
130 def annotate
130 @annotate = @page.annotate(params[:version])
131 @annotate = @page.annotate(params[:version])
131 render_404 unless @annotate
132 render_404 unless @annotate
132 end
133 end
133
134
134 # Removes a wiki page and its history
135 # Removes a wiki page and its history
135 # Children can be either set as root pages, removed or reassigned to another parent page
136 # Children can be either set as root pages, removed or reassigned to another parent page
136 def destroy
137 def destroy
137 return render_403 unless editable?
138 return render_403 unless editable?
138
139
139 @descendants_count = @page.descendants.size
140 @descendants_count = @page.descendants.size
140 if @descendants_count > 0
141 if @descendants_count > 0
141 case params[:todo]
142 case params[:todo]
142 when 'nullify'
143 when 'nullify'
143 # Nothing to do
144 # Nothing to do
144 when 'destroy'
145 when 'destroy'
145 # Removes all its descendants
146 # Removes all its descendants
146 @page.descendants.each(&:destroy)
147 @page.descendants.each(&:destroy)
147 when 'reassign'
148 when 'reassign'
148 # Reassign children to another parent page
149 # Reassign children to another parent page
149 reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
150 reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
150 return unless reassign_to
151 return unless reassign_to
151 @page.children.each do |child|
152 @page.children.each do |child|
152 child.update_attribute(:parent, reassign_to)
153 child.update_attribute(:parent, reassign_to)
153 end
154 end
154 else
155 else
155 @reassignable_to = @wiki.pages - @page.self_and_descendants
156 @reassignable_to = @wiki.pages - @page.self_and_descendants
156 return
157 return
157 end
158 end
158 end
159 end
159 @page.destroy
160 @page.destroy
160 redirect_to :action => 'special', :id => @project, :page => 'Page_index'
161 redirect_to :action => 'special', :id => @project, :page => 'Page_index'
161 end
162 end
162
163
163 # display special pages
164 # display special pages
164 def special
165 def special
165 page_title = params[:page].downcase
166 page_title = params[:page].downcase
166 case page_title
167 case page_title
167 # show pages index, sorted by title
168 # show pages index, sorted by title
168 when 'page_index', 'date_index'
169 when 'page_index', 'date_index'
169 # eager load information about last updates, without loading text
170 # eager load information about last updates, without loading text
170 @pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
171 @pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
171 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
172 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
172 :order => 'title'
173 :order => 'title'
173 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
174 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
174 @pages_by_parent_id = @pages.group_by(&:parent_id)
175 @pages_by_parent_id = @pages.group_by(&:parent_id)
175 # export wiki to a single html file
176 # export wiki to a single html file
176 when 'export'
177 when 'export'
177 @pages = @wiki.pages.find :all, :order => 'title'
178 @pages = @wiki.pages.find :all, :order => 'title'
178 export = render_to_string :action => 'export_multiple', :layout => false
179 export = render_to_string :action => 'export_multiple', :layout => false
179 send_data(export, :type => 'text/html', :filename => "wiki.html")
180 send_data(export, :type => 'text/html', :filename => "wiki.html")
180 return
181 return
181 else
182 else
182 # requested special page doesn't exist, redirect to default page
183 # requested special page doesn't exist, redirect to default page
183 redirect_to :action => 'index', :id => @project, :page => nil and return
184 redirect_to :action => 'index', :id => @project, :page => nil and return
184 end
185 end
185 render :action => "special_#{page_title}"
186 render :action => "special_#{page_title}"
186 end
187 end
187
188
188 def preview
189 def preview
189 page = @wiki.find_page(params[:page])
190 page = @wiki.find_page(params[:page])
190 # page is nil when previewing a new page
191 # page is nil when previewing a new page
191 return render_403 unless page.nil? || editable?(page)
192 return render_403 unless page.nil? || editable?(page)
192 if page
193 if page
193 @attachements = page.attachments
194 @attachements = page.attachments
194 @previewed = page.content
195 @previewed = page.content
195 end
196 end
196 @text = params[:content][:text]
197 @text = params[:content][:text]
197 render :partial => 'common/preview'
198 render :partial => 'common/preview'
198 end
199 end
199
200
200 def add_attachment
201 def add_attachment
201 return render_403 unless editable?
202 return render_403 unless editable?
202 attach_files(@page, params[:attachments])
203 attach_files(@page, params[:attachments])
203 redirect_to :action => 'index', :page => @page.title
204 redirect_to :action => 'index', :page => @page.title
204 end
205 end
205
206
206 private
207 private
207
208
208 def find_wiki
209 def find_wiki
209 @project = Project.find(params[:id])
210 @project = Project.find(params[:id])
210 @wiki = @project.wiki
211 @wiki = @project.wiki
211 render_404 unless @wiki
212 render_404 unless @wiki
212 rescue ActiveRecord::RecordNotFound
213 rescue ActiveRecord::RecordNotFound
213 render_404
214 render_404
214 end
215 end
215
216
216 # Finds the requested page and returns a 404 error if it doesn't exist
217 # Finds the requested page and returns a 404 error if it doesn't exist
217 def find_existing_page
218 def find_existing_page
218 @page = @wiki.find_page(params[:page])
219 @page = @wiki.find_page(params[:page])
219 render_404 if @page.nil?
220 render_404 if @page.nil?
220 end
221 end
221
222
222 # Returns true if the current user is allowed to edit the page, otherwise false
223 # Returns true if the current user is allowed to edit the page, otherwise false
223 def editable?(page = @page)
224 def editable?(page = @page)
224 page.editable_by?(User.current)
225 page.editable_by?(User.current)
225 end
226 end
226
227
227 # Returns the default content of a new wiki page
228 # Returns the default content of a new wiki page
228 def initial_page_content(page)
229 def initial_page_content(page)
229 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
230 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
230 extend helper unless self.instance_of?(helper)
231 extend helper unless self.instance_of?(helper)
231 helper.instance_method(:initial_page_content).bind(self).call(page)
232 helper.instance_method(:initial_page_content).bind(self).call(page)
232 end
233 end
233 end
234 end
@@ -1,385 +1,387
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 class Mailer < ActionMailer::Base
18 class Mailer < ActionMailer::Base
19 helper :application
19 helper :application
20 helper :issues
20 helper :issues
21 helper :custom_fields
21 helper :custom_fields
22
22
23 include ActionController::UrlWriter
23 include ActionController::UrlWriter
24 include Redmine::I18n
24 include Redmine::I18n
25
25
26 def self.default_url_options
26 def self.default_url_options
27 h = Setting.host_name
27 h = Setting.host_name
28 h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
28 h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
29 { :host => h, :protocol => Setting.protocol }
29 { :host => h, :protocol => Setting.protocol }
30 end
30 end
31
31
32 # Builds a tmail object used to email recipients of the added issue.
32 # Builds a tmail object used to email recipients of the added issue.
33 #
33 #
34 # Example:
34 # Example:
35 # issue_add(issue) => tmail object
35 # issue_add(issue) => tmail object
36 # Mailer.deliver_issue_add(issue) => sends an email to issue recipients
36 # Mailer.deliver_issue_add(issue) => sends an email to issue recipients
37 def issue_add(issue)
37 def issue_add(issue)
38 redmine_headers 'Project' => issue.project.identifier,
38 redmine_headers 'Project' => issue.project.identifier,
39 'Issue-Id' => issue.id,
39 'Issue-Id' => issue.id,
40 'Issue-Author' => issue.author.login
40 'Issue-Author' => issue.author.login
41 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
41 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
42 message_id issue
42 message_id issue
43 recipients issue.recipients
43 recipients issue.recipients
44 cc(issue.watcher_recipients - @recipients)
44 cc(issue.watcher_recipients - @recipients)
45 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
45 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
46 body :issue => issue,
46 body :issue => issue,
47 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
47 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
48 end
48 end
49
49
50 # Builds a tmail object used to email recipients of the edited issue.
50 # Builds a tmail object used to email recipients of the edited issue.
51 #
51 #
52 # Example:
52 # Example:
53 # issue_edit(journal) => tmail object
53 # issue_edit(journal) => tmail object
54 # Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
54 # Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
55 def issue_edit(journal)
55 def issue_edit(journal)
56 issue = journal.journalized.reload
56 issue = journal.journalized.reload
57 redmine_headers 'Project' => issue.project.identifier,
57 redmine_headers 'Project' => issue.project.identifier,
58 'Issue-Id' => issue.id,
58 'Issue-Id' => issue.id,
59 'Issue-Author' => issue.author.login
59 'Issue-Author' => issue.author.login
60 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
60 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
61 message_id journal
61 message_id journal
62 references issue
62 references issue
63 @author = journal.user
63 @author = journal.user
64 recipients issue.recipients
64 recipients issue.recipients
65 # Watchers in cc
65 # Watchers in cc
66 cc(issue.watcher_recipients - @recipients)
66 cc(issue.watcher_recipients - @recipients)
67 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
67 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
68 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
68 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
69 s << issue.subject
69 s << issue.subject
70 subject s
70 subject s
71 body :issue => issue,
71 body :issue => issue,
72 :journal => journal,
72 :journal => journal,
73 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
73 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
74 end
74 end
75
75
76 def reminder(user, issues, days)
76 def reminder(user, issues, days)
77 set_language_if_valid user.language
77 set_language_if_valid user.language
78 recipients user.mail
78 recipients user.mail
79 subject l(:mail_subject_reminder, issues.size)
79 subject l(:mail_subject_reminder, issues.size)
80 body :issues => issues,
80 body :issues => issues,
81 :days => days,
81 :days => days,
82 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
82 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
83 end
83 end
84
84
85 # Builds a tmail object used to email users belonging to the added document's project.
85 # Builds a tmail object used to email users belonging to the added document's project.
86 #
86 #
87 # Example:
87 # Example:
88 # document_added(document) => tmail object
88 # document_added(document) => tmail object
89 # Mailer.deliver_document_added(document) => sends an email to the document's project recipients
89 # Mailer.deliver_document_added(document) => sends an email to the document's project recipients
90 def document_added(document)
90 def document_added(document)
91 redmine_headers 'Project' => document.project.identifier
91 redmine_headers 'Project' => document.project.identifier
92 recipients document.project.recipients
92 recipients document.project.recipients
93 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
93 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
94 body :document => document,
94 body :document => document,
95 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
95 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
96 end
96 end
97
97
98 # Builds a tmail object used to email recipients of a project when an attachements are added.
98 # Builds a tmail object used to email recipients of a project when an attachements are added.
99 #
99 #
100 # Example:
100 # Example:
101 # attachments_added(attachments) => tmail object
101 # attachments_added(attachments) => tmail object
102 # Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients
102 # Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients
103 def attachments_added(attachments)
103 def attachments_added(attachments)
104 container = attachments.first.container
104 container = attachments.first.container
105 added_to = ''
105 added_to = ''
106 added_to_url = ''
106 added_to_url = ''
107 case container.class.name
107 case container.class.name
108 when 'Project'
108 when 'Project'
109 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
109 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
110 added_to = "#{l(:label_project)}: #{container}"
110 added_to = "#{l(:label_project)}: #{container}"
111 when 'Version'
111 when 'Version'
112 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
112 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
113 added_to = "#{l(:label_version)}: #{container.name}"
113 added_to = "#{l(:label_version)}: #{container.name}"
114 when 'Document'
114 when 'Document'
115 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
115 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
116 added_to = "#{l(:label_document)}: #{container.title}"
116 added_to = "#{l(:label_document)}: #{container.title}"
117 end
117 end
118 redmine_headers 'Project' => container.project.identifier
118 redmine_headers 'Project' => container.project.identifier
119 recipients container.project.recipients
119 recipients container.project.recipients
120 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
120 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
121 body :attachments => attachments,
121 body :attachments => attachments,
122 :added_to => added_to,
122 :added_to => added_to,
123 :added_to_url => added_to_url
123 :added_to_url => added_to_url
124 end
124 end
125
125
126 # Builds a tmail object used to email recipients of a news' project when a news item is added.
126 # Builds a tmail object used to email recipients of a news' project when a news item is added.
127 #
127 #
128 # Example:
128 # Example:
129 # news_added(news) => tmail object
129 # news_added(news) => tmail object
130 # Mailer.deliver_news_added(news) => sends an email to the news' project recipients
130 # Mailer.deliver_news_added(news) => sends an email to the news' project recipients
131 def news_added(news)
131 def news_added(news)
132 redmine_headers 'Project' => news.project.identifier
132 redmine_headers 'Project' => news.project.identifier
133 message_id news
133 message_id news
134 recipients news.project.recipients
134 recipients news.project.recipients
135 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
135 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
136 body :news => news,
136 body :news => news,
137 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
137 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
138 end
138 end
139
139
140 # Builds a tmail object used to email the specified recipients of the specified message that was posted.
140 # Builds a tmail object used to email the specified recipients of the specified message that was posted.
141 #
141 #
142 # Example:
142 # Example:
143 # message_posted(message, recipients) => tmail object
143 # message_posted(message, recipients) => tmail object
144 # Mailer.deliver_message_posted(message, recipients) => sends an email to the recipients
144 # Mailer.deliver_message_posted(message, recipients) => sends an email to the recipients
145 def message_posted(message, recipients)
145 def message_posted(message, recipients)
146 redmine_headers 'Project' => message.project.identifier,
146 redmine_headers 'Project' => message.project.identifier,
147 'Topic-Id' => (message.parent_id || message.id)
147 'Topic-Id' => (message.parent_id || message.id)
148 message_id message
148 message_id message
149 references message.parent unless message.parent.nil?
149 references message.parent unless message.parent.nil?
150 recipients(recipients)
150 recipients(recipients)
151 subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
151 subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
152 body :message => message,
152 body :message => message,
153 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
153 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
154 end
154 end
155
155
156 # Builds a tmail object used to email the recipients of a project of the specified wiki content was added.
156 # Builds a tmail object used to email the recipients of a project of the specified wiki content was added.
157 #
157 #
158 # Example:
158 # Example:
159 # wiki_content_added(wiki_content) => tmail object
159 # wiki_content_added(wiki_content) => tmail object
160 # Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients
160 # Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients
161 def wiki_content_added(wiki_content)
161 def wiki_content_added(wiki_content)
162 redmine_headers 'Project' => wiki_content.project.identifier,
162 redmine_headers 'Project' => wiki_content.project.identifier,
163 'Wiki-Page-Id' => wiki_content.page.id
163 'Wiki-Page-Id' => wiki_content.page.id
164 message_id wiki_content
164 message_id wiki_content
165 recipients wiki_content.project.recipients
165 recipients wiki_content.project.recipients
166 cc(wiki_content.page.wiki.watcher_recipients - recipients)
166 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :page => wiki_content.page.pretty_title)}"
167 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :page => wiki_content.page.pretty_title)}"
167 body :wiki_content => wiki_content,
168 body :wiki_content => wiki_content,
168 :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title)
169 :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title)
169 end
170 end
170
171
171 # Builds a tmail object used to email the recipients of a project of the specified wiki content was updated.
172 # Builds a tmail object used to email the recipients of a project of the specified wiki content was updated.
172 #
173 #
173 # Example:
174 # Example:
174 # wiki_content_updated(wiki_content) => tmail object
175 # wiki_content_updated(wiki_content) => tmail object
175 # Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients
176 # Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients
176 def wiki_content_updated(wiki_content)
177 def wiki_content_updated(wiki_content)
177 redmine_headers 'Project' => wiki_content.project.identifier,
178 redmine_headers 'Project' => wiki_content.project.identifier,
178 'Wiki-Page-Id' => wiki_content.page.id
179 'Wiki-Page-Id' => wiki_content.page.id
179 message_id wiki_content
180 message_id wiki_content
180 recipients wiki_content.project.recipients
181 recipients wiki_content.project.recipients
182 cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients)
181 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :page => wiki_content.page.pretty_title)}"
183 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :page => wiki_content.page.pretty_title)}"
182 body :wiki_content => wiki_content,
184 body :wiki_content => wiki_content,
183 :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title),
185 :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title),
184 :wiki_diff_url => url_for(:controller => 'wiki', :action => 'diff', :id => wiki_content.project, :page => wiki_content.page.title, :version => wiki_content.version)
186 :wiki_diff_url => url_for(:controller => 'wiki', :action => 'diff', :id => wiki_content.project, :page => wiki_content.page.title, :version => wiki_content.version)
185 end
187 end
186
188
187 # Builds a tmail object used to email the specified user their account information.
189 # Builds a tmail object used to email the specified user their account information.
188 #
190 #
189 # Example:
191 # Example:
190 # account_information(user, password) => tmail object
192 # account_information(user, password) => tmail object
191 # Mailer.deliver_account_information(user, password) => sends account information to the user
193 # Mailer.deliver_account_information(user, password) => sends account information to the user
192 def account_information(user, password)
194 def account_information(user, password)
193 set_language_if_valid user.language
195 set_language_if_valid user.language
194 recipients user.mail
196 recipients user.mail
195 subject l(:mail_subject_register, Setting.app_title)
197 subject l(:mail_subject_register, Setting.app_title)
196 body :user => user,
198 body :user => user,
197 :password => password,
199 :password => password,
198 :login_url => url_for(:controller => 'account', :action => 'login')
200 :login_url => url_for(:controller => 'account', :action => 'login')
199 end
201 end
200
202
201 # Builds a tmail object used to email all active administrators of an account activation request.
203 # Builds a tmail object used to email all active administrators of an account activation request.
202 #
204 #
203 # Example:
205 # Example:
204 # account_activation_request(user) => tmail object
206 # account_activation_request(user) => tmail object
205 # Mailer.deliver_account_activation_request(user)=> sends an email to all active administrators
207 # Mailer.deliver_account_activation_request(user)=> sends an email to all active administrators
206 def account_activation_request(user)
208 def account_activation_request(user)
207 # Send the email to all active administrators
209 # Send the email to all active administrators
208 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
210 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
209 subject l(:mail_subject_account_activation_request, Setting.app_title)
211 subject l(:mail_subject_account_activation_request, Setting.app_title)
210 body :user => user,
212 body :user => user,
211 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
213 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
212 end
214 end
213
215
214 # Builds a tmail object used to email the specified user that their account was activated by an administrator.
216 # Builds a tmail object used to email the specified user that their account was activated by an administrator.
215 #
217 #
216 # Example:
218 # Example:
217 # account_activated(user) => tmail object
219 # account_activated(user) => tmail object
218 # Mailer.deliver_account_activated(user) => sends an email to the registered user
220 # Mailer.deliver_account_activated(user) => sends an email to the registered user
219 def account_activated(user)
221 def account_activated(user)
220 set_language_if_valid user.language
222 set_language_if_valid user.language
221 recipients user.mail
223 recipients user.mail
222 subject l(:mail_subject_register, Setting.app_title)
224 subject l(:mail_subject_register, Setting.app_title)
223 body :user => user,
225 body :user => user,
224 :login_url => url_for(:controller => 'account', :action => 'login')
226 :login_url => url_for(:controller => 'account', :action => 'login')
225 end
227 end
226
228
227 def lost_password(token)
229 def lost_password(token)
228 set_language_if_valid(token.user.language)
230 set_language_if_valid(token.user.language)
229 recipients token.user.mail
231 recipients token.user.mail
230 subject l(:mail_subject_lost_password, Setting.app_title)
232 subject l(:mail_subject_lost_password, Setting.app_title)
231 body :token => token,
233 body :token => token,
232 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
234 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
233 end
235 end
234
236
235 def register(token)
237 def register(token)
236 set_language_if_valid(token.user.language)
238 set_language_if_valid(token.user.language)
237 recipients token.user.mail
239 recipients token.user.mail
238 subject l(:mail_subject_register, Setting.app_title)
240 subject l(:mail_subject_register, Setting.app_title)
239 body :token => token,
241 body :token => token,
240 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
242 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
241 end
243 end
242
244
243 def test(user)
245 def test(user)
244 set_language_if_valid(user.language)
246 set_language_if_valid(user.language)
245 recipients user.mail
247 recipients user.mail
246 subject 'Redmine test'
248 subject 'Redmine test'
247 body :url => url_for(:controller => 'welcome')
249 body :url => url_for(:controller => 'welcome')
248 end
250 end
249
251
250 # Overrides default deliver! method to prevent from sending an email
252 # Overrides default deliver! method to prevent from sending an email
251 # with no recipient, cc or bcc
253 # with no recipient, cc or bcc
252 def deliver!(mail = @mail)
254 def deliver!(mail = @mail)
253 return false if (recipients.nil? || recipients.empty?) &&
255 return false if (recipients.nil? || recipients.empty?) &&
254 (cc.nil? || cc.empty?) &&
256 (cc.nil? || cc.empty?) &&
255 (bcc.nil? || bcc.empty?)
257 (bcc.nil? || bcc.empty?)
256
258
257 # Set Message-Id and References
259 # Set Message-Id and References
258 if @message_id_object
260 if @message_id_object
259 mail.message_id = self.class.message_id_for(@message_id_object)
261 mail.message_id = self.class.message_id_for(@message_id_object)
260 end
262 end
261 if @references_objects
263 if @references_objects
262 mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
264 mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
263 end
265 end
264 super(mail)
266 super(mail)
265 end
267 end
266
268
267 # Sends reminders to issue assignees
269 # Sends reminders to issue assignees
268 # Available options:
270 # Available options:
269 # * :days => how many days in the future to remind about (defaults to 7)
271 # * :days => how many days in the future to remind about (defaults to 7)
270 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
272 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
271 # * :project => id or identifier of project to process (defaults to all projects)
273 # * :project => id or identifier of project to process (defaults to all projects)
272 def self.reminders(options={})
274 def self.reminders(options={})
273 days = options[:days] || 7
275 days = options[:days] || 7
274 project = options[:project] ? Project.find(options[:project]) : nil
276 project = options[:project] ? Project.find(options[:project]) : nil
275 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
277 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
276
278
277 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
279 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
278 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
280 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
279 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
281 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
280 s << "#{Issue.table_name}.project_id = #{project.id}" if project
282 s << "#{Issue.table_name}.project_id = #{project.id}" if project
281 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
283 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
282
284
283 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
285 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
284 :conditions => s.conditions
286 :conditions => s.conditions
285 ).group_by(&:assigned_to)
287 ).group_by(&:assigned_to)
286 issues_by_assignee.each do |assignee, issues|
288 issues_by_assignee.each do |assignee, issues|
287 deliver_reminder(assignee, issues, days) unless assignee.nil?
289 deliver_reminder(assignee, issues, days) unless assignee.nil?
288 end
290 end
289 end
291 end
290
292
291 private
293 private
292 def initialize_defaults(method_name)
294 def initialize_defaults(method_name)
293 super
295 super
294 set_language_if_valid Setting.default_language
296 set_language_if_valid Setting.default_language
295 from Setting.mail_from
297 from Setting.mail_from
296
298
297 # Common headers
299 # Common headers
298 headers 'X-Mailer' => 'Redmine',
300 headers 'X-Mailer' => 'Redmine',
299 'X-Redmine-Host' => Setting.host_name,
301 'X-Redmine-Host' => Setting.host_name,
300 'X-Redmine-Site' => Setting.app_title,
302 'X-Redmine-Site' => Setting.app_title,
301 'Precedence' => 'bulk',
303 'Precedence' => 'bulk',
302 'Auto-Submitted' => 'auto-generated'
304 'Auto-Submitted' => 'auto-generated'
303 end
305 end
304
306
305 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
307 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
306 def redmine_headers(h)
308 def redmine_headers(h)
307 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
309 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
308 end
310 end
309
311
310 # Overrides the create_mail method
312 # Overrides the create_mail method
311 def create_mail
313 def create_mail
312 # Removes the current user from the recipients and cc
314 # Removes the current user from the recipients and cc
313 # if he doesn't want to receive notifications about what he does
315 # if he doesn't want to receive notifications about what he does
314 @author ||= User.current
316 @author ||= User.current
315 if @author.pref[:no_self_notified]
317 if @author.pref[:no_self_notified]
316 recipients.delete(@author.mail) if recipients
318 recipients.delete(@author.mail) if recipients
317 cc.delete(@author.mail) if cc
319 cc.delete(@author.mail) if cc
318 end
320 end
319 # Blind carbon copy recipients
321 # Blind carbon copy recipients
320 if Setting.bcc_recipients?
322 if Setting.bcc_recipients?
321 bcc([recipients, cc].flatten.compact.uniq)
323 bcc([recipients, cc].flatten.compact.uniq)
322 recipients []
324 recipients []
323 cc []
325 cc []
324 end
326 end
325 super
327 super
326 end
328 end
327
329
328 # Renders a message with the corresponding layout
330 # Renders a message with the corresponding layout
329 def render_message(method_name, body)
331 def render_message(method_name, body)
330 layout = method_name.to_s.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
332 layout = method_name.to_s.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
331 body[:content_for_layout] = render(:file => method_name, :body => body)
333 body[:content_for_layout] = render(:file => method_name, :body => body)
332 ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
334 ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
333 end
335 end
334
336
335 # for the case of plain text only
337 # for the case of plain text only
336 def body(*params)
338 def body(*params)
337 value = super(*params)
339 value = super(*params)
338 if Setting.plain_text_mail?
340 if Setting.plain_text_mail?
339 templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
341 templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
340 unless String === @body or templates.empty?
342 unless String === @body or templates.empty?
341 template = File.basename(templates.first)
343 template = File.basename(templates.first)
342 @body[:content_for_layout] = render(:file => template, :body => @body)
344 @body[:content_for_layout] = render(:file => template, :body => @body)
343 @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
345 @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
344 return @body
346 return @body
345 end
347 end
346 end
348 end
347 return value
349 return value
348 end
350 end
349
351
350 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
352 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
351 def self.controller_path
353 def self.controller_path
352 ''
354 ''
353 end unless respond_to?('controller_path')
355 end unless respond_to?('controller_path')
354
356
355 # Returns a predictable Message-Id for the given object
357 # Returns a predictable Message-Id for the given object
356 def self.message_id_for(object)
358 def self.message_id_for(object)
357 # id + timestamp should reduce the odds of a collision
359 # id + timestamp should reduce the odds of a collision
358 # as far as we don't send multiple emails for the same object
360 # as far as we don't send multiple emails for the same object
359 timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
361 timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
360 hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}"
362 hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}"
361 host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
363 host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
362 host = "#{::Socket.gethostname}.redmine" if host.empty?
364 host = "#{::Socket.gethostname}.redmine" if host.empty?
363 "<#{hash}@#{host}>"
365 "<#{hash}@#{host}>"
364 end
366 end
365
367
366 private
368 private
367
369
368 def message_id(object)
370 def message_id(object)
369 @message_id_object = object
371 @message_id_object = object
370 end
372 end
371
373
372 def references(object)
374 def references(object)
373 @references_objects ||= []
375 @references_objects ||= []
374 @references_objects << object
376 @references_objects << object
375 end
377 end
376 end
378 end
377
379
378 # Patch TMail so that message_id is not overwritten
380 # Patch TMail so that message_id is not overwritten
379 module TMail
381 module TMail
380 class Mail
382 class Mail
381 def add_message_id( fqdn = nil )
383 def add_message_id( fqdn = nil )
382 self.message_id ||= ::TMail::new_message_id(fqdn)
384 self.message_id ||= ::TMail::new_message_id(fqdn)
383 end
385 end
384 end
386 end
385 end
387 end
@@ -1,73 +1,75
1 # redMine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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 class Wiki < ActiveRecord::Base
18 class Wiki < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title'
20 has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title'
21 has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
21 has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
22
22
23 acts_as_watchable
24
23 validates_presence_of :start_page
25 validates_presence_of :start_page
24 validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/
26 validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/
25
27
26 # find the page with the given title
28 # find the page with the given title
27 # if page doesn't exist, return a new page
29 # if page doesn't exist, return a new page
28 def find_or_new_page(title)
30 def find_or_new_page(title)
29 title = start_page if title.blank?
31 title = start_page if title.blank?
30 find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title))
32 find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title))
31 end
33 end
32
34
33 # find the page with the given title
35 # find the page with the given title
34 def find_page(title, options = {})
36 def find_page(title, options = {})
35 title = start_page if title.blank?
37 title = start_page if title.blank?
36 title = Wiki.titleize(title)
38 title = Wiki.titleize(title)
37 page = pages.find_by_title(title)
39 page = pages.find_by_title(title)
38 if !page && !(options[:with_redirect] == false)
40 if !page && !(options[:with_redirect] == false)
39 # search for a redirect
41 # search for a redirect
40 redirect = redirects.find_by_title(title)
42 redirect = redirects.find_by_title(title)
41 page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
43 page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
42 end
44 end
43 page
45 page
44 end
46 end
45
47
46 # Finds a page by title
48 # Finds a page by title
47 # The given string can be of one of the forms: "title" or "project:title"
49 # The given string can be of one of the forms: "title" or "project:title"
48 # Examples:
50 # Examples:
49 # Wiki.find_page("bar", project => foo)
51 # Wiki.find_page("bar", project => foo)
50 # Wiki.find_page("foo:bar")
52 # Wiki.find_page("foo:bar")
51 def self.find_page(title, options = {})
53 def self.find_page(title, options = {})
52 project = options[:project]
54 project = options[:project]
53 if title.to_s =~ %r{^([^\:]+)\:(.*)$}
55 if title.to_s =~ %r{^([^\:]+)\:(.*)$}
54 project_identifier, title = $1, $2
56 project_identifier, title = $1, $2
55 project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier)
57 project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier)
56 end
58 end
57 if project && project.wiki
59 if project && project.wiki
58 page = project.wiki.find_page(title)
60 page = project.wiki.find_page(title)
59 if page && page.content
61 if page && page.content
60 page
62 page
61 end
63 end
62 end
64 end
63 end
65 end
64
66
65 # turn a string into a valid page title
67 # turn a string into a valid page title
66 def self.titleize(title)
68 def self.titleize(title)
67 # replace spaces with _ and remove unwanted caracters
69 # replace spaces with _ and remove unwanted caracters
68 title = title.gsub(/\s+/, '_').delete(',./?;|:') if title
70 title = title.gsub(/\s+/, '_').delete(',./?;|:') if title
69 # upcase the first letter
71 # upcase the first letter
70 title = (title.slice(0..0).upcase + (title.slice(1..-1) || '')) if title
72 title = (title.slice(0..0).upcase + (title.slice(1..-1) || '')) if title
71 title
73 title
72 end
74 end
73 end
75 end
@@ -1,188 +1,189
1 # redMine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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 require 'enumerator'
19 require 'enumerator'
20
20
21 class WikiPage < ActiveRecord::Base
21 class WikiPage < ActiveRecord::Base
22 belongs_to :wiki
22 belongs_to :wiki
23 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
23 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
24 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
24 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
25 acts_as_tree :dependent => :nullify, :order => 'title'
25 acts_as_tree :dependent => :nullify, :order => 'title'
26
26
27 acts_as_watchable
27 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
28 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
28 :description => :text,
29 :description => :text,
29 :datetime => :created_on,
30 :datetime => :created_on,
30 :url => Proc.new {|o| {:controller => 'wiki', :id => o.wiki.project_id, :page => o.title}}
31 :url => Proc.new {|o| {:controller => 'wiki', :id => o.wiki.project_id, :page => o.title}}
31
32
32 acts_as_searchable :columns => ['title', 'text'],
33 acts_as_searchable :columns => ['title', 'text'],
33 :include => [{:wiki => :project}, :content],
34 :include => [{:wiki => :project}, :content],
34 :project_key => "#{Wiki.table_name}.project_id"
35 :project_key => "#{Wiki.table_name}.project_id"
35
36
36 attr_accessor :redirect_existing_links
37 attr_accessor :redirect_existing_links
37
38
38 validates_presence_of :title
39 validates_presence_of :title
39 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
40 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
40 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
41 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
41 validates_associated :content
42 validates_associated :content
42
43
43 def title=(value)
44 def title=(value)
44 value = Wiki.titleize(value)
45 value = Wiki.titleize(value)
45 @previous_title = read_attribute(:title) if @previous_title.blank?
46 @previous_title = read_attribute(:title) if @previous_title.blank?
46 write_attribute(:title, value)
47 write_attribute(:title, value)
47 end
48 end
48
49
49 def before_save
50 def before_save
50 self.title = Wiki.titleize(title)
51 self.title = Wiki.titleize(title)
51 # Manage redirects if the title has changed
52 # Manage redirects if the title has changed
52 if !@previous_title.blank? && (@previous_title != title) && !new_record?
53 if !@previous_title.blank? && (@previous_title != title) && !new_record?
53 # Update redirects that point to the old title
54 # Update redirects that point to the old title
54 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
55 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
55 r.redirects_to = title
56 r.redirects_to = title
56 r.title == r.redirects_to ? r.destroy : r.save
57 r.title == r.redirects_to ? r.destroy : r.save
57 end
58 end
58 # Remove redirects for the new title
59 # Remove redirects for the new title
59 wiki.redirects.find_all_by_title(title).each(&:destroy)
60 wiki.redirects.find_all_by_title(title).each(&:destroy)
60 # Create a redirect to the new title
61 # Create a redirect to the new title
61 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
62 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
62 @previous_title = nil
63 @previous_title = nil
63 end
64 end
64 end
65 end
65
66
66 def before_destroy
67 def before_destroy
67 # Remove redirects to this page
68 # Remove redirects to this page
68 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
69 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
69 end
70 end
70
71
71 def pretty_title
72 def pretty_title
72 WikiPage.pretty_title(title)
73 WikiPage.pretty_title(title)
73 end
74 end
74
75
75 def content_for_version(version=nil)
76 def content_for_version(version=nil)
76 result = content.versions.find_by_version(version.to_i) if version
77 result = content.versions.find_by_version(version.to_i) if version
77 result ||= content
78 result ||= content
78 result
79 result
79 end
80 end
80
81
81 def diff(version_to=nil, version_from=nil)
82 def diff(version_to=nil, version_from=nil)
82 version_to = version_to ? version_to.to_i : self.content.version
83 version_to = version_to ? version_to.to_i : self.content.version
83 version_from = version_from ? version_from.to_i : version_to - 1
84 version_from = version_from ? version_from.to_i : version_to - 1
84 version_to, version_from = version_from, version_to unless version_from < version_to
85 version_to, version_from = version_from, version_to unless version_from < version_to
85
86
86 content_to = content.versions.find_by_version(version_to)
87 content_to = content.versions.find_by_version(version_to)
87 content_from = content.versions.find_by_version(version_from)
88 content_from = content.versions.find_by_version(version_from)
88
89
89 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
90 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
90 end
91 end
91
92
92 def annotate(version=nil)
93 def annotate(version=nil)
93 version = version ? version.to_i : self.content.version
94 version = version ? version.to_i : self.content.version
94 c = content.versions.find_by_version(version)
95 c = content.versions.find_by_version(version)
95 c ? WikiAnnotate.new(c) : nil
96 c ? WikiAnnotate.new(c) : nil
96 end
97 end
97
98
98 def self.pretty_title(str)
99 def self.pretty_title(str)
99 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
100 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
100 end
101 end
101
102
102 def project
103 def project
103 wiki.project
104 wiki.project
104 end
105 end
105
106
106 def text
107 def text
107 content.text if content
108 content.text if content
108 end
109 end
109
110
110 # Returns true if usr is allowed to edit the page, otherwise false
111 # Returns true if usr is allowed to edit the page, otherwise false
111 def editable_by?(usr)
112 def editable_by?(usr)
112 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
113 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
113 end
114 end
114
115
115 def attachments_deletable?(usr=User.current)
116 def attachments_deletable?(usr=User.current)
116 editable_by?(usr) && super(usr)
117 editable_by?(usr) && super(usr)
117 end
118 end
118
119
119 def parent_title
120 def parent_title
120 @parent_title || (self.parent && self.parent.pretty_title)
121 @parent_title || (self.parent && self.parent.pretty_title)
121 end
122 end
122
123
123 def parent_title=(t)
124 def parent_title=(t)
124 @parent_title = t
125 @parent_title = t
125 parent_page = t.blank? ? nil : self.wiki.find_page(t)
126 parent_page = t.blank? ? nil : self.wiki.find_page(t)
126 self.parent = parent_page
127 self.parent = parent_page
127 end
128 end
128
129
129 protected
130 protected
130
131
131 def validate
132 def validate
132 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
133 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
133 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
134 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
134 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
135 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
135 end
136 end
136 end
137 end
137
138
138 class WikiDiff
139 class WikiDiff
139 attr_reader :diff, :words, :content_to, :content_from
140 attr_reader :diff, :words, :content_to, :content_from
140
141
141 def initialize(content_to, content_from)
142 def initialize(content_to, content_from)
142 @content_to = content_to
143 @content_to = content_to
143 @content_from = content_from
144 @content_from = content_from
144 @words = content_to.text.split(/(\s+)/)
145 @words = content_to.text.split(/(\s+)/)
145 @words = @words.select {|word| word != ' '}
146 @words = @words.select {|word| word != ' '}
146 words_from = content_from.text.split(/(\s+)/)
147 words_from = content_from.text.split(/(\s+)/)
147 words_from = words_from.select {|word| word != ' '}
148 words_from = words_from.select {|word| word != ' '}
148 @diff = words_from.diff @words
149 @diff = words_from.diff @words
149 end
150 end
150 end
151 end
151
152
152 class WikiAnnotate
153 class WikiAnnotate
153 attr_reader :lines, :content
154 attr_reader :lines, :content
154
155
155 def initialize(content)
156 def initialize(content)
156 @content = content
157 @content = content
157 current = content
158 current = content
158 current_lines = current.text.split(/\r?\n/)
159 current_lines = current.text.split(/\r?\n/)
159 @lines = current_lines.collect {|t| [nil, nil, t]}
160 @lines = current_lines.collect {|t| [nil, nil, t]}
160 positions = []
161 positions = []
161 current_lines.size.times {|i| positions << i}
162 current_lines.size.times {|i| positions << i}
162 while (current.previous)
163 while (current.previous)
163 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
164 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
164 d.each_slice(3) do |s|
165 d.each_slice(3) do |s|
165 sign, line = s[0], s[1]
166 sign, line = s[0], s[1]
166 if sign == '+' && positions[line] && positions[line] != -1
167 if sign == '+' && positions[line] && positions[line] != -1
167 if @lines[positions[line]][0].nil?
168 if @lines[positions[line]][0].nil?
168 @lines[positions[line]][0] = current.version
169 @lines[positions[line]][0] = current.version
169 @lines[positions[line]][1] = current.author
170 @lines[positions[line]][1] = current.author
170 end
171 end
171 end
172 end
172 end
173 end
173 d.each_slice(3) do |s|
174 d.each_slice(3) do |s|
174 sign, line = s[0], s[1]
175 sign, line = s[0], s[1]
175 if sign == '-'
176 if sign == '-'
176 positions.insert(line, -1)
177 positions.insert(line, -1)
177 else
178 else
178 positions[line] = nil
179 positions[line] = nil
179 end
180 end
180 end
181 end
181 positions.compact!
182 positions.compact!
182 # Stop if every line is annotated
183 # Stop if every line is annotated
183 break unless @lines.detect { |line| line[0].nil? }
184 break unless @lines.detect { |line| line[0].nil? }
184 current = current.previous
185 current = current.previous
185 end
186 end
186 @lines.each { |line| line[0] ||= current.version }
187 @lines.each { |line| line[0] ||= current.version }
187 end
188 end
188 end
189 end
@@ -1,60 +1,61
1 <div class="contextual">
1 <div class="contextual">
2 <% if @editable %>
2 <% if @editable %>
3 <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if @content.version == @page.content.version %>
3 <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if @content.version == @page.content.version %>
4 <%= watcher_tag(@page, User.current) %>
4 <%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :page => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
5 <%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :page => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
5 <%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :page => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
6 <%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :page => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
6 <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :page => @page.title}, :class => 'icon icon-move') if @content.version == @page.content.version %>
7 <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :page => @page.title}, :class => 'icon icon-move') if @content.version == @page.content.version %>
7 <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :page => @page.title}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
8 <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :page => @page.title}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
8 <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :page => @page.title, :version => @content.version }, :class => 'icon icon-cancel') if @content.version < @page.content.version %>
9 <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :page => @page.title, :version => @content.version }, :class => 'icon icon-cancel') if @content.version < @page.content.version %>
9 <% end %>
10 <% end %>
10 <%= link_to_if_authorized(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
11 <%= link_to_if_authorized(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
11 </div>
12 </div>
12
13
13 <%= breadcrumb(@page.ancestors.reverse.collect {|parent| link_to h(parent.pretty_title), {:page => parent.title}}) %>
14 <%= breadcrumb(@page.ancestors.reverse.collect {|parent| link_to h(parent.pretty_title), {:page => parent.title}}) %>
14
15
15 <% if @content.version != @page.content.version %>
16 <% if @content.version != @page.content.version %>
16 <p>
17 <p>
17 <%= link_to(('&#171; ' + l(:label_previous)), :action => 'index', :page => @page.title, :version => (@content.version - 1)) + " - " if @content.version > 1 %>
18 <%= link_to(('&#171; ' + l(:label_previous)), :action => 'index', :page => @page.title, :version => (@content.version - 1)) + " - " if @content.version > 1 %>
18 <%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %>
19 <%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %>
19 <%= '(' + link_to('diff', :controller => 'wiki', :action => 'diff', :page => @page.title, :version => @content.version) + ')' if @content.version > 1 %> -
20 <%= '(' + link_to('diff', :controller => 'wiki', :action => 'diff', :page => @page.title, :version => @content.version) + ')' if @content.version > 1 %> -
20 <%= link_to((l(:label_next) + ' &#187;'), :action => 'index', :page => @page.title, :version => (@content.version + 1)) + " - " if @content.version < @page.content.version %>
21 <%= link_to((l(:label_next) + ' &#187;'), :action => 'index', :page => @page.title, :version => (@content.version + 1)) + " - " if @content.version < @page.content.version %>
21 <%= link_to(l(:label_current_version), :action => 'index', :page => @page.title) %>
22 <%= link_to(l(:label_current_version), :action => 'index', :page => @page.title) %>
22 <br />
23 <br />
23 <em><%= @content.author ? @content.author.name : "anonyme" %>, <%= format_time(@content.updated_on) %> </em><br />
24 <em><%= @content.author ? @content.author.name : "anonyme" %>, <%= format_time(@content.updated_on) %> </em><br />
24 <%=h @content.comments %>
25 <%=h @content.comments %>
25 </p>
26 </p>
26 <hr />
27 <hr />
27 <% end %>
28 <% end %>
28
29
29 <%= render(:partial => "wiki/content", :locals => {:content => @content}) %>
30 <%= render(:partial => "wiki/content", :locals => {:content => @content}) %>
30
31
31 <%= link_to_attachments @page %>
32 <%= link_to_attachments @page %>
32
33
33 <% if @editable && authorize_for('wiki', 'add_attachment') %>
34 <% if @editable && authorize_for('wiki', 'add_attachment') %>
34 <div id="wiki_add_attachment">
35 <div id="wiki_add_attachment">
35 <p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;",
36 <p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;",
36 :id => 'attach_files_link' %></p>
37 :id => 'attach_files_link' %></p>
37 <% form_tag({ :controller => 'wiki', :action => 'add_attachment', :page => @page.title }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %>
38 <% form_tag({ :controller => 'wiki', :action => 'add_attachment', :page => @page.title }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %>
38 <div class="box">
39 <div class="box">
39 <p><%= render :partial => 'attachments/form' %></p>
40 <p><%= render :partial => 'attachments/form' %></p>
40 </div>
41 </div>
41 <%= submit_tag l(:button_add) %>
42 <%= submit_tag l(:button_add) %>
42 <%= link_to l(:button_cancel), {}, :onclick => "Element.hide('add_attachment_form'); Element.show('attach_files_link'); return false;" %>
43 <%= link_to l(:button_cancel), {}, :onclick => "Element.hide('add_attachment_form'); Element.show('attach_files_link'); return false;" %>
43 <% end %>
44 <% end %>
44 </div>
45 </div>
45 <% end %>
46 <% end %>
46
47
47 <% other_formats_links do |f| %>
48 <% other_formats_links do |f| %>
48 <%= f.link_to 'HTML', :url => {:page => @page.title, :version => @content.version} %>
49 <%= f.link_to 'HTML', :url => {:page => @page.title, :version => @content.version} %>
49 <%= f.link_to 'TXT', :url => {:page => @page.title, :version => @content.version} %>
50 <%= f.link_to 'TXT', :url => {:page => @page.title, :version => @content.version} %>
50 <% end %>
51 <% end %>
51
52
52 <% content_for :header_tags do %>
53 <% content_for :header_tags do %>
53 <%= stylesheet_link_tag 'scm' %>
54 <%= stylesheet_link_tag 'scm' %>
54 <% end %>
55 <% end %>
55
56
56 <% content_for :sidebar do %>
57 <% content_for :sidebar do %>
57 <%= render :partial => 'sidebar' %>
58 <%= render :partial => 'sidebar' %>
58 <% end %>
59 <% end %>
59
60
60 <% html_title @page.pretty_title %>
61 <% html_title @page.pretty_title %>
@@ -1,29 +1,33
1 <div class="contextual">
2 <%= watcher_tag(@wiki, User.current) %>
3 </div>
4
1 <h2><%= l(:label_index_by_date) %></h2>
5 <h2><%= l(:label_index_by_date) %></h2>
2
6
3 <% if @pages.empty? %>
7 <% if @pages.empty? %>
4 <p class="nodata"><%= l(:label_no_data) %></p>
8 <p class="nodata"><%= l(:label_no_data) %></p>
5 <% end %>
9 <% end %>
6
10
7 <% @pages_by_date.keys.sort.reverse.each do |date| %>
11 <% @pages_by_date.keys.sort.reverse.each do |date| %>
8 <h3><%= format_date(date) %></h3>
12 <h3><%= format_date(date) %></h3>
9 <ul>
13 <ul>
10 <% @pages_by_date[date].each do |page| %>
14 <% @pages_by_date[date].each do |page| %>
11 <li><%= link_to page.pretty_title, :action => 'index', :page => page.title %></li>
15 <li><%= link_to page.pretty_title, :action => 'index', :page => page.title %></li>
12 <% end %>
16 <% end %>
13 </ul>
17 </ul>
14 <% end %>
18 <% end %>
15
19
16 <% content_for :sidebar do %>
20 <% content_for :sidebar do %>
17 <%= render :partial => 'sidebar' %>
21 <%= render :partial => 'sidebar' %>
18 <% end %>
22 <% end %>
19
23
20 <% unless @pages.empty? %>
24 <% unless @pages.empty? %>
21 <% other_formats_links do |f| %>
25 <% other_formats_links do |f| %>
22 <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :key => User.current.rss_key} %>
26 <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :key => User.current.rss_key} %>
23 <%= f.link_to 'HTML', :url => {:action => 'special', :page => 'export'} %>
27 <%= f.link_to 'HTML', :url => {:action => 'special', :page => 'export'} %>
24 <% end %>
28 <% end %>
25 <% end %>
29 <% end %>
26
30
27 <% content_for :header_tags do %>
31 <% content_for :header_tags do %>
28 <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :format => 'atom', :key => User.current.rss_key) %>
32 <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :format => 'atom', :key => User.current.rss_key) %>
29 <% end %>
33 <% end %>
@@ -1,22 +1,26
1 <div class="contextual">
2 <%= watcher_tag(@wiki, User.current) %>
3 </div>
4
1 <h2><%= l(:label_index_by_title) %></h2>
5 <h2><%= l(:label_index_by_title) %></h2>
2
6
3 <% if @pages.empty? %>
7 <% if @pages.empty? %>
4 <p class="nodata"><%= l(:label_no_data) %></p>
8 <p class="nodata"><%= l(:label_no_data) %></p>
5 <% end %>
9 <% end %>
6
10
7 <%= render_page_hierarchy(@pages_by_parent_id) %>
11 <%= render_page_hierarchy(@pages_by_parent_id) %>
8
12
9 <% content_for :sidebar do %>
13 <% content_for :sidebar do %>
10 <%= render :partial => 'sidebar' %>
14 <%= render :partial => 'sidebar' %>
11 <% end %>
15 <% end %>
12
16
13 <% unless @pages.empty? %>
17 <% unless @pages.empty? %>
14 <% other_formats_links do |f| %>
18 <% other_formats_links do |f| %>
15 <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :key => User.current.rss_key} %>
19 <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :key => User.current.rss_key} %>
16 <%= f.link_to 'HTML', :url => {:action => 'special', :page => 'export'} %>
20 <%= f.link_to 'HTML', :url => {:action => 'special', :page => 'export'} %>
17 <% end %>
21 <% end %>
18 <% end %>
22 <% end %>
19
23
20 <% content_for :header_tags do %>
24 <% content_for :header_tags do %>
21 <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :format => 'atom', :key => User.current.rss_key) %>
25 <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :format => 'atom', :key => User.current.rss_key) %>
22 <% end %>
26 <% end %>
General Comments 0
You need to be logged in to leave comments. Login now