##// END OF EJS Templates
Fixed test failures on wiki_page updated_on eager loading (#10996)....
Jean-Philippe Lang -
r9544:9aef7a32425d
parent child
Show More
@@ -1,234 +1,234
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 include Redmine::SafeAttributes
22 include Redmine::SafeAttributes
23
23
24 belongs_to :wiki
24 belongs_to :wiki
25 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
25 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
26 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
26 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
27 acts_as_tree :dependent => :nullify, :order => 'title'
27 acts_as_tree :dependent => :nullify, :order => 'title'
28
28
29 acts_as_watchable
29 acts_as_watchable
30 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
30 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
31 :description => :text,
31 :description => :text,
32 :datetime => :created_on,
32 :datetime => :created_on,
33 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
33 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
34
34
35 acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"],
35 acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"],
36 :include => [{:wiki => :project}, :content],
36 :include => [{:wiki => :project}, :content],
37 :permission => :view_wiki_pages,
37 :permission => :view_wiki_pages,
38 :project_key => "#{Wiki.table_name}.project_id"
38 :project_key => "#{Wiki.table_name}.project_id"
39
39
40 attr_accessor :redirect_existing_links
40 attr_accessor :redirect_existing_links
41
41
42 validates_presence_of :title
42 validates_presence_of :title
43 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
43 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
44 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
44 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
45 validates_associated :content
45 validates_associated :content
46
46
47 validate :validate_parent_title
47 validate :validate_parent_title
48 before_destroy :remove_redirects
48 before_destroy :remove_redirects
49 before_save :handle_redirects
49 before_save :handle_redirects
50
50
51 # eager load information about last updates, without loading text
51 # eager load information about last updates, without loading text
52 scope :with_updated_on, {
52 scope :with_updated_on, {
53 :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
53 :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
54 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id"
54 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id"
55 }
55 }
56
56
57 # Wiki pages that are protected by default
57 # Wiki pages that are protected by default
58 DEFAULT_PROTECTED_PAGES = %w(sidebar)
58 DEFAULT_PROTECTED_PAGES = %w(sidebar)
59
59
60 safe_attributes 'parent_id',
60 safe_attributes 'parent_id',
61 :if => lambda {|page, user| page.new_record? || user.allowed_to?(:rename_wiki_pages, page.project)}
61 :if => lambda {|page, user| page.new_record? || user.allowed_to?(:rename_wiki_pages, page.project)}
62
62
63 def initialize(attributes=nil, *args)
63 def initialize(attributes=nil, *args)
64 super
64 super
65 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
65 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
66 self.protected = true
66 self.protected = true
67 end
67 end
68 end
68 end
69
69
70 def visible?(user=User.current)
70 def visible?(user=User.current)
71 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
71 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
72 end
72 end
73
73
74 def title=(value)
74 def title=(value)
75 value = Wiki.titleize(value)
75 value = Wiki.titleize(value)
76 @previous_title = read_attribute(:title) if @previous_title.blank?
76 @previous_title = read_attribute(:title) if @previous_title.blank?
77 write_attribute(:title, value)
77 write_attribute(:title, value)
78 end
78 end
79
79
80 def handle_redirects
80 def handle_redirects
81 self.title = Wiki.titleize(title)
81 self.title = Wiki.titleize(title)
82 # Manage redirects if the title has changed
82 # Manage redirects if the title has changed
83 if !@previous_title.blank? && (@previous_title != title) && !new_record?
83 if !@previous_title.blank? && (@previous_title != title) && !new_record?
84 # Update redirects that point to the old title
84 # Update redirects that point to the old title
85 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
85 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
86 r.redirects_to = title
86 r.redirects_to = title
87 r.title == r.redirects_to ? r.destroy : r.save
87 r.title == r.redirects_to ? r.destroy : r.save
88 end
88 end
89 # Remove redirects for the new title
89 # Remove redirects for the new title
90 wiki.redirects.find_all_by_title(title).each(&:destroy)
90 wiki.redirects.find_all_by_title(title).each(&:destroy)
91 # Create a redirect to the new title
91 # Create a redirect to the new title
92 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
92 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
93 @previous_title = nil
93 @previous_title = nil
94 end
94 end
95 end
95 end
96
96
97 def remove_redirects
97 def remove_redirects
98 # Remove redirects to this page
98 # Remove redirects to this page
99 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
99 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
100 end
100 end
101
101
102 def pretty_title
102 def pretty_title
103 WikiPage.pretty_title(title)
103 WikiPage.pretty_title(title)
104 end
104 end
105
105
106 def content_for_version(version=nil)
106 def content_for_version(version=nil)
107 result = content.versions.find_by_version(version.to_i) if version
107 result = content.versions.find_by_version(version.to_i) if version
108 result ||= content
108 result ||= content
109 result
109 result
110 end
110 end
111
111
112 def diff(version_to=nil, version_from=nil)
112 def diff(version_to=nil, version_from=nil)
113 version_to = version_to ? version_to.to_i : self.content.version
113 version_to = version_to ? version_to.to_i : self.content.version
114 version_from = version_from ? version_from.to_i : version_to - 1
114 version_from = version_from ? version_from.to_i : version_to - 1
115 version_to, version_from = version_from, version_to unless version_from < version_to
115 version_to, version_from = version_from, version_to unless version_from < version_to
116
116
117 content_to = content.versions.find_by_version(version_to)
117 content_to = content.versions.find_by_version(version_to)
118 content_from = content.versions.find_by_version(version_from)
118 content_from = content.versions.find_by_version(version_from)
119
119
120 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
120 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
121 end
121 end
122
122
123 def annotate(version=nil)
123 def annotate(version=nil)
124 version = version ? version.to_i : self.content.version
124 version = version ? version.to_i : self.content.version
125 c = content.versions.find_by_version(version)
125 c = content.versions.find_by_version(version)
126 c ? WikiAnnotate.new(c) : nil
126 c ? WikiAnnotate.new(c) : nil
127 end
127 end
128
128
129 def self.pretty_title(str)
129 def self.pretty_title(str)
130 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
130 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
131 end
131 end
132
132
133 def project
133 def project
134 wiki.project
134 wiki.project
135 end
135 end
136
136
137 def text
137 def text
138 content.text if content
138 content.text if content
139 end
139 end
140
140
141 def updated_on
141 def updated_on
142 unless @updated_on
142 unless @updated_on
143 if time = read_attribute(:updated_on)
143 if time = read_attribute(:updated_on)
144 # content updated_on was eager loaded with the page
144 # content updated_on was eager loaded with the page
145 begin
145 begin
146 @updated_on = Time.zone ? Time.zone.parse(time.to_s) : Time.parse(time.to_s)
146 @updated_on = (self.class.default_timezone == :utc ? Time.parse(time.to_s).utc : Time.parse(time.to_s).localtime)
147 rescue
147 rescue
148 end
148 end
149 else
149 else
150 @updated_on = content && content.updated_on
150 @updated_on = content && content.updated_on
151 end
151 end
152 end
152 end
153 @updated_on
153 @updated_on
154 end
154 end
155
155
156 # Returns true if usr is allowed to edit the page, otherwise false
156 # Returns true if usr is allowed to edit the page, otherwise false
157 def editable_by?(usr)
157 def editable_by?(usr)
158 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
158 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
159 end
159 end
160
160
161 def attachments_deletable?(usr=User.current)
161 def attachments_deletable?(usr=User.current)
162 editable_by?(usr) && super(usr)
162 editable_by?(usr) && super(usr)
163 end
163 end
164
164
165 def parent_title
165 def parent_title
166 @parent_title || (self.parent && self.parent.pretty_title)
166 @parent_title || (self.parent && self.parent.pretty_title)
167 end
167 end
168
168
169 def parent_title=(t)
169 def parent_title=(t)
170 @parent_title = t
170 @parent_title = t
171 parent_page = t.blank? ? nil : self.wiki.find_page(t)
171 parent_page = t.blank? ? nil : self.wiki.find_page(t)
172 self.parent = parent_page
172 self.parent = parent_page
173 end
173 end
174
174
175 protected
175 protected
176
176
177 def validate_parent_title
177 def validate_parent_title
178 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
178 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
179 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
179 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
180 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
180 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
181 end
181 end
182 end
182 end
183
183
184 class WikiDiff < Redmine::Helpers::Diff
184 class WikiDiff < Redmine::Helpers::Diff
185 attr_reader :content_to, :content_from
185 attr_reader :content_to, :content_from
186
186
187 def initialize(content_to, content_from)
187 def initialize(content_to, content_from)
188 @content_to = content_to
188 @content_to = content_to
189 @content_from = content_from
189 @content_from = content_from
190 super(content_to.text, content_from.text)
190 super(content_to.text, content_from.text)
191 end
191 end
192 end
192 end
193
193
194 class WikiAnnotate
194 class WikiAnnotate
195 attr_reader :lines, :content
195 attr_reader :lines, :content
196
196
197 def initialize(content)
197 def initialize(content)
198 @content = content
198 @content = content
199 current = content
199 current = content
200 current_lines = current.text.split(/\r?\n/)
200 current_lines = current.text.split(/\r?\n/)
201 @lines = current_lines.collect {|t| [nil, nil, t]}
201 @lines = current_lines.collect {|t| [nil, nil, t]}
202 positions = []
202 positions = []
203 current_lines.size.times {|i| positions << i}
203 current_lines.size.times {|i| positions << i}
204 while (current.previous)
204 while (current.previous)
205 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
205 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
206 d.each_slice(3) do |s|
206 d.each_slice(3) do |s|
207 sign, line = s[0], s[1]
207 sign, line = s[0], s[1]
208 if sign == '+' && positions[line] && positions[line] != -1
208 if sign == '+' && positions[line] && positions[line] != -1
209 if @lines[positions[line]][0].nil?
209 if @lines[positions[line]][0].nil?
210 @lines[positions[line]][0] = current.version
210 @lines[positions[line]][0] = current.version
211 @lines[positions[line]][1] = current.author
211 @lines[positions[line]][1] = current.author
212 end
212 end
213 end
213 end
214 end
214 end
215 d.each_slice(3) do |s|
215 d.each_slice(3) do |s|
216 sign, line = s[0], s[1]
216 sign, line = s[0], s[1]
217 if sign == '-'
217 if sign == '-'
218 positions.insert(line, -1)
218 positions.insert(line, -1)
219 else
219 else
220 positions[line] = nil
220 positions[line] = nil
221 end
221 end
222 end
222 end
223 positions.compact!
223 positions.compact!
224 # Stop if every line is annotated
224 # Stop if every line is annotated
225 break unless @lines.detect { |line| line[0].nil? }
225 break unless @lines.detect { |line| line[0].nil? }
226 current = current.previous
226 current = current.previous
227 end
227 end
228 @lines.each { |line|
228 @lines.each { |line|
229 line[0] ||= current.version
229 line[0] ||= current.version
230 # if the last known version is > 1 (eg. history was cleared), we don't know the author
230 # if the last known version is > 1 (eg. history was cleared), we don't know the author
231 line[1] ||= current.author if current.version == 1
231 line[1] ||= current.author if current.version == 1
232 }
232 }
233 end
233 end
234 end
234 end
General Comments 0
You need to be logged in to leave comments. Login now