##// END OF EJS Templates
Use \A and \z in validation regexps....
Jean-Philippe Lang -
r10734:0bd70d468051
parent child
Show More
@@ -1,251 +1,251
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'diff'
19 19 require 'enumerator'
20 20
21 21 class WikiPage < ActiveRecord::Base
22 22 include Redmine::SafeAttributes
23 23
24 24 belongs_to :wiki
25 25 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
26 26 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
27 27 acts_as_tree :dependent => :nullify, :order => 'title'
28 28
29 29 acts_as_watchable
30 30 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
31 31 :description => :text,
32 32 :datetime => :created_on,
33 33 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
34 34
35 35 acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"],
36 36 :include => [{:wiki => :project}, :content],
37 37 :permission => :view_wiki_pages,
38 38 :project_key => "#{Wiki.table_name}.project_id"
39 39
40 40 attr_accessor :redirect_existing_links
41 41
42 42 validates_presence_of :title
43 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
43 validates_format_of :title, :with => /\A[^,\.\/\?\;\|\s]*\z/
44 44 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
45 45 validates_associated :content
46 46
47 47 validate :validate_parent_title
48 48 before_destroy :remove_redirects
49 49 before_save :handle_redirects
50 50
51 51 # eager load information about last updates, without loading text
52 52 scope :with_updated_on, lambda {
53 53 select("#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version").
54 54 joins("LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id")
55 55 }
56 56
57 57 # Wiki pages that are protected by default
58 58 DEFAULT_PROTECTED_PAGES = %w(sidebar)
59 59
60 60 safe_attributes 'parent_id', 'parent_title',
61 61 :if => lambda {|page, user| page.new_record? || user.allowed_to?(:rename_wiki_pages, page.project)}
62 62
63 63 def initialize(attributes=nil, *args)
64 64 super
65 65 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
66 66 self.protected = true
67 67 end
68 68 end
69 69
70 70 def visible?(user=User.current)
71 71 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
72 72 end
73 73
74 74 def title=(value)
75 75 value = Wiki.titleize(value)
76 76 @previous_title = read_attribute(:title) if @previous_title.blank?
77 77 write_attribute(:title, value)
78 78 end
79 79
80 80 def handle_redirects
81 81 self.title = Wiki.titleize(title)
82 82 # Manage redirects if the title has changed
83 83 if !@previous_title.blank? && (@previous_title != title) && !new_record?
84 84 # Update redirects that point to the old title
85 85 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
86 86 r.redirects_to = title
87 87 r.title == r.redirects_to ? r.destroy : r.save
88 88 end
89 89 # Remove redirects for the new title
90 90 wiki.redirects.find_all_by_title(title).each(&:destroy)
91 91 # Create a redirect to the new title
92 92 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
93 93 @previous_title = nil
94 94 end
95 95 end
96 96
97 97 def remove_redirects
98 98 # Remove redirects to this page
99 99 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
100 100 end
101 101
102 102 def pretty_title
103 103 WikiPage.pretty_title(title)
104 104 end
105 105
106 106 def content_for_version(version=nil)
107 107 result = content.versions.find_by_version(version.to_i) if version
108 108 result ||= content
109 109 result
110 110 end
111 111
112 112 def diff(version_to=nil, version_from=nil)
113 113 version_to = version_to ? version_to.to_i : self.content.version
114 114 content_to = content.versions.find_by_version(version_to)
115 115 content_from = version_from ? content.versions.find_by_version(version_from.to_i) : content_to.try(:previous)
116 116 return nil unless content_to && content_from
117 117
118 118 if content_from.version > content_to.version
119 119 content_to, content_from = content_from, content_to
120 120 end
121 121
122 122 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
123 123 end
124 124
125 125 def annotate(version=nil)
126 126 version = version ? version.to_i : self.content.version
127 127 c = content.versions.find_by_version(version)
128 128 c ? WikiAnnotate.new(c) : nil
129 129 end
130 130
131 131 def self.pretty_title(str)
132 132 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
133 133 end
134 134
135 135 def project
136 136 wiki.project
137 137 end
138 138
139 139 def text
140 140 content.text if content
141 141 end
142 142
143 143 def updated_on
144 144 unless @updated_on
145 145 if time = read_attribute(:updated_on)
146 146 # content updated_on was eager loaded with the page
147 147 begin
148 148 @updated_on = (self.class.default_timezone == :utc ? Time.parse(time.to_s).utc : Time.parse(time.to_s).localtime)
149 149 rescue
150 150 end
151 151 else
152 152 @updated_on = content && content.updated_on
153 153 end
154 154 end
155 155 @updated_on
156 156 end
157 157
158 158 # Returns true if usr is allowed to edit the page, otherwise false
159 159 def editable_by?(usr)
160 160 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
161 161 end
162 162
163 163 def attachments_deletable?(usr=User.current)
164 164 editable_by?(usr) && super(usr)
165 165 end
166 166
167 167 def parent_title
168 168 @parent_title || (self.parent && self.parent.pretty_title)
169 169 end
170 170
171 171 def parent_title=(t)
172 172 @parent_title = t
173 173 parent_page = t.blank? ? nil : self.wiki.find_page(t)
174 174 self.parent = parent_page
175 175 end
176 176
177 177 # Saves the page and its content if text was changed
178 178 def save_with_content
179 179 ret = nil
180 180 transaction do
181 181 if new_record?
182 182 # Rails automatically saves associated content
183 183 ret = save
184 184 else
185 185 ret = save && (content.text_changed? ? content.save : true)
186 186 end
187 187 raise ActiveRecord::Rollback unless ret
188 188 end
189 189 ret
190 190 end
191 191
192 192 protected
193 193
194 194 def validate_parent_title
195 195 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
196 196 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
197 197 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
198 198 end
199 199 end
200 200
201 201 class WikiDiff < Redmine::Helpers::Diff
202 202 attr_reader :content_to, :content_from
203 203
204 204 def initialize(content_to, content_from)
205 205 @content_to = content_to
206 206 @content_from = content_from
207 207 super(content_to.text, content_from.text)
208 208 end
209 209 end
210 210
211 211 class WikiAnnotate
212 212 attr_reader :lines, :content
213 213
214 214 def initialize(content)
215 215 @content = content
216 216 current = content
217 217 current_lines = current.text.split(/\r?\n/)
218 218 @lines = current_lines.collect {|t| [nil, nil, t]}
219 219 positions = []
220 220 current_lines.size.times {|i| positions << i}
221 221 while (current.previous)
222 222 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
223 223 d.each_slice(3) do |s|
224 224 sign, line = s[0], s[1]
225 225 if sign == '+' && positions[line] && positions[line] != -1
226 226 if @lines[positions[line]][0].nil?
227 227 @lines[positions[line]][0] = current.version
228 228 @lines[positions[line]][1] = current.author
229 229 end
230 230 end
231 231 end
232 232 d.each_slice(3) do |s|
233 233 sign, line = s[0], s[1]
234 234 if sign == '-'
235 235 positions.insert(line, -1)
236 236 else
237 237 positions[line] = nil
238 238 end
239 239 end
240 240 positions.compact!
241 241 # Stop if every line is annotated
242 242 break unless @lines.detect { |line| line[0].nil? }
243 243 current = current.previous
244 244 end
245 245 @lines.each { |line|
246 246 line[0] ||= current.version
247 247 # if the last known version is > 1 (eg. history was cleared), we don't know the author
248 248 line[1] ||= current.author if current.version == 1
249 249 }
250 250 end
251 251 end
General Comments 0
You need to be logged in to leave comments. Login now