##// END OF EJS Templates
Rails4: replace deprecated find_all_by_* at WikiPage model...
Toshi MARUYAMA -
r12258:95d86daef3aa
parent child
Show More
@@ -1,252 +1,252
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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 => /\A[^,\.\/\?\;\|\s]*\z/
43 validates_format_of :title, :with => /\A[^,\.\/\?\;\|\s]*\z/
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, lambda {
52 scope :with_updated_on, lambda {
53 select("#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version").
53 select("#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version").
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', 'parent_title',
60 safe_attributes 'parent_id', 'parent_title',
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.where(: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.where(:title => title).all.each(&:destroy)
90 wiki.redirects.where(:title => title).all.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.where(: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 content_to = content.versions.find_by_version(version_to)
114 content_to = content.versions.find_by_version(version_to)
115 content_from = version_from ? content.versions.find_by_version(version_from.to_i) : content_to.try(:previous)
115 content_from = version_from ? content.versions.find_by_version(version_from.to_i) : content_to.try(:previous)
116 return nil unless content_to && content_from
116 return nil unless content_to && content_from
117
117
118 if content_from.version > content_to.version
118 if content_from.version > content_to.version
119 content_to, content_from = content_from, content_to
119 content_to, content_from = content_from, content_to
120 end
120 end
121
121
122 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
122 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
123 end
123 end
124
124
125 def annotate(version=nil)
125 def annotate(version=nil)
126 version = version ? version.to_i : self.content.version
126 version = version ? version.to_i : self.content.version
127 c = content.versions.find_by_version(version)
127 c = content.versions.find_by_version(version)
128 c ? WikiAnnotate.new(c) : nil
128 c ? WikiAnnotate.new(c) : nil
129 end
129 end
130
130
131 def self.pretty_title(str)
131 def self.pretty_title(str)
132 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
132 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
133 end
133 end
134
134
135 def project
135 def project
136 wiki.project
136 wiki.project
137 end
137 end
138
138
139 def text
139 def text
140 content.text if content
140 content.text if content
141 end
141 end
142
142
143 def updated_on
143 def updated_on
144 unless @updated_on
144 unless @updated_on
145 if time = read_attribute(:updated_on)
145 if time = read_attribute(:updated_on)
146 # content updated_on was eager loaded with the page
146 # content updated_on was eager loaded with the page
147 begin
147 begin
148 @updated_on = (self.class.default_timezone == :utc ? Time.parse(time.to_s).utc : Time.parse(time.to_s).localtime)
148 @updated_on = (self.class.default_timezone == :utc ? Time.parse(time.to_s).utc : Time.parse(time.to_s).localtime)
149 rescue
149 rescue
150 end
150 end
151 else
151 else
152 @updated_on = content && content.updated_on
152 @updated_on = content && content.updated_on
153 end
153 end
154 end
154 end
155 @updated_on
155 @updated_on
156 end
156 end
157
157
158 # Returns true if usr is allowed to edit the page, otherwise false
158 # Returns true if usr is allowed to edit the page, otherwise false
159 def editable_by?(usr)
159 def editable_by?(usr)
160 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
160 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
161 end
161 end
162
162
163 def attachments_deletable?(usr=User.current)
163 def attachments_deletable?(usr=User.current)
164 editable_by?(usr) && super(usr)
164 editable_by?(usr) && super(usr)
165 end
165 end
166
166
167 def parent_title
167 def parent_title
168 @parent_title || (self.parent && self.parent.pretty_title)
168 @parent_title || (self.parent && self.parent.pretty_title)
169 end
169 end
170
170
171 def parent_title=(t)
171 def parent_title=(t)
172 @parent_title = t
172 @parent_title = t
173 parent_page = t.blank? ? nil : self.wiki.find_page(t)
173 parent_page = t.blank? ? nil : self.wiki.find_page(t)
174 self.parent = parent_page
174 self.parent = parent_page
175 end
175 end
176
176
177 # Saves the page and its content if text was changed
177 # Saves the page and its content if text was changed
178 def save_with_content(content)
178 def save_with_content(content)
179 ret = nil
179 ret = nil
180 transaction do
180 transaction do
181 self.content = content
181 self.content = content
182 if new_record?
182 if new_record?
183 # Rails automatically saves associated content
183 # Rails automatically saves associated content
184 ret = save
184 ret = save
185 else
185 else
186 ret = save && (content.text_changed? ? content.save : true)
186 ret = save && (content.text_changed? ? content.save : true)
187 end
187 end
188 raise ActiveRecord::Rollback unless ret
188 raise ActiveRecord::Rollback unless ret
189 end
189 end
190 ret
190 ret
191 end
191 end
192
192
193 protected
193 protected
194
194
195 def validate_parent_title
195 def validate_parent_title
196 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
196 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
197 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
197 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
198 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
198 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
199 end
199 end
200 end
200 end
201
201
202 class WikiDiff < Redmine::Helpers::Diff
202 class WikiDiff < Redmine::Helpers::Diff
203 attr_reader :content_to, :content_from
203 attr_reader :content_to, :content_from
204
204
205 def initialize(content_to, content_from)
205 def initialize(content_to, content_from)
206 @content_to = content_to
206 @content_to = content_to
207 @content_from = content_from
207 @content_from = content_from
208 super(content_to.text, content_from.text)
208 super(content_to.text, content_from.text)
209 end
209 end
210 end
210 end
211
211
212 class WikiAnnotate
212 class WikiAnnotate
213 attr_reader :lines, :content
213 attr_reader :lines, :content
214
214
215 def initialize(content)
215 def initialize(content)
216 @content = content
216 @content = content
217 current = content
217 current = content
218 current_lines = current.text.split(/\r?\n/)
218 current_lines = current.text.split(/\r?\n/)
219 @lines = current_lines.collect {|t| [nil, nil, t]}
219 @lines = current_lines.collect {|t| [nil, nil, t]}
220 positions = []
220 positions = []
221 current_lines.size.times {|i| positions << i}
221 current_lines.size.times {|i| positions << i}
222 while (current.previous)
222 while (current.previous)
223 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
223 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
224 d.each_slice(3) do |s|
224 d.each_slice(3) do |s|
225 sign, line = s[0], s[1]
225 sign, line = s[0], s[1]
226 if sign == '+' && positions[line] && positions[line] != -1
226 if sign == '+' && positions[line] && positions[line] != -1
227 if @lines[positions[line]][0].nil?
227 if @lines[positions[line]][0].nil?
228 @lines[positions[line]][0] = current.version
228 @lines[positions[line]][0] = current.version
229 @lines[positions[line]][1] = current.author
229 @lines[positions[line]][1] = current.author
230 end
230 end
231 end
231 end
232 end
232 end
233 d.each_slice(3) do |s|
233 d.each_slice(3) do |s|
234 sign, line = s[0], s[1]
234 sign, line = s[0], s[1]
235 if sign == '-'
235 if sign == '-'
236 positions.insert(line, -1)
236 positions.insert(line, -1)
237 else
237 else
238 positions[line] = nil
238 positions[line] = nil
239 end
239 end
240 end
240 end
241 positions.compact!
241 positions.compact!
242 # Stop if every line is annotated
242 # Stop if every line is annotated
243 break unless @lines.detect { |line| line[0].nil? }
243 break unless @lines.detect { |line| line[0].nil? }
244 current = current.previous
244 current = current.previous
245 end
245 end
246 @lines.each { |line|
246 @lines.each { |line|
247 line[0] ||= current.version
247 line[0] ||= current.version
248 # if the last known version is > 1 (eg. history was cleared), we don't know the author
248 # if the last known version is > 1 (eg. history was cleared), we don't know the author
249 line[1] ||= current.author if current.version == 1
249 line[1] ||= current.author if current.version == 1
250 }
250 }
251 end
251 end
252 end
252 end
General Comments 0
You need to be logged in to leave comments. Login now