Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 415
- Log:
Add "|" as another character which is interpreted as a closure to a named
reference syntax Textile tag. Without this, the Textile link corrector can
trip up in tables unless the author puts a space before the "|" separating
one cell from another immediately after a named reference syntax link:|"This would fail":failure|Another cell|
[failure]http://www.test.com/
- Author:
- rool
- Date:
- Mon Mar 21 18:09:53 +0000 2011
- Size:
- 11809 Bytes
1 | class Page < ActiveRecord::Base |
2 | belongs_to :web |
3 | has_many :revisions, :order => 'id', :dependent => :destroy |
4 | has_many :wiki_references, :order => 'referenced_name' |
5 | has_one :current_revision, :class_name => 'Revision', :order => 'id DESC' |
6 | |
7 | def name |
8 | read_attribute(:name).as_utf8 |
9 | end |
10 | |
11 | def revise(content, name, time, author, renderer) |
12 | revisions_size = new_record? ? 0 : revisions.size |
13 | if (revisions_size > 0) and content == current_revision.content and name == self.name |
14 | raise Instiki::ValidationError.new( |
15 | "You have tried to save page '#{name}' without changing its content") |
16 | end |
17 | |
18 | self.name = name |
19 | author = Author.new(author.to_s) unless author.is_a?(Author) |
20 | |
21 | # 2011-03-12 (ADH): Assumption of Textile processing; patch inter-page |
22 | # links expressed as Textile links not Wiki links, so |
23 | # that Instiki's references mechanism recognises them |
24 | # and pages are less likely to be erroneously declared |
25 | # to be orphans. |
26 | # |
27 | content = patch_interpage_textile_links( content ) |
28 | |
29 | # Try to render content to make sure that markup engine can take it, |
30 | renderer.revision = Revision.new( |
31 | :page => self, :content => content, :author => author, :revised_at => time) |
32 | renderer.display_content(update_references = true) |
33 | |
34 | # A user may change a page, look at it and make some more changes - several times. |
35 | # Not to record every such iteration as a new revision, if the previous revision was done |
36 | # by the same author, not more than 30 minutes ago, then update the last revision instead of |
37 | # creating a new one |
38 | if (revisions_size > 0) && continous_revision?(time, author) |
39 | current_revision.update_attributes(:content => content, :revised_at => time) |
40 | else |
41 | revisions.build(:content => content, :author => author, :revised_at => time) |
42 | end |
43 | save |
44 | self |
45 | end |
46 | |
47 | def rollback(revision_number, time, author_ip, renderer) |
48 | roll_back_revision = self.revisions[revision_number] |
49 | if roll_back_revision.nil? |
50 | raise Instiki::ValidationError.new("Revision #{revision_number} not found") |
51 | end |
52 | author = Author.new(roll_back_revision.author.name, author_ip) |
53 | revise(roll_back_revision.content, self.name, time, author, renderer) |
54 | end |
55 | |
56 | def revisions? |
57 | revisions.size > 1 |
58 | end |
59 | |
60 | def previous_revision(revision) |
61 | revision_index = revisions.each_with_index do |rev, index| |
62 | if rev.id == revision.id |
63 | break index |
64 | else |
65 | nil |
66 | end |
67 | end |
68 | if revision_index.nil? or revision_index == 0 |
69 | nil |
70 | else |
71 | revisions[revision_index - 1] |
72 | end |
73 | end |
74 | |
75 | def references |
76 | web.select.pages_that_reference(name) |
77 | end |
78 | |
79 | def wiki_words |
80 | wiki_references.select { |ref| ref.wiki_word? }.map { |ref| ref.referenced_name } |
81 | end |
82 | |
83 | def categories |
84 | wiki_references.select { |ref| ref.category? }.map { |ref| ref.referenced_name } |
85 | end |
86 | |
87 | def linked_from |
88 | web.select.pages_that_link_to(name) |
89 | end |
90 | |
91 | def redirects |
92 | wiki_references.select { |ref| ref.redirected_page? }.map { |ref| ref.referenced_name } |
93 | end |
94 | |
95 | def included_from |
96 | web.select.pages_that_include(name) |
97 | end |
98 | |
99 | # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page". |
100 | def plain_name |
101 | web.brackets_only? ? name.escapeHTML.html_safe : WikiWords.separate(name).escapeHTML.html_safe |
102 | end |
103 | |
104 | LOCKING_PERIOD = 30.minutes |
105 | |
106 | def lock(time, locked_by) |
107 | update_attributes(:locked_at => time, :locked_by => locked_by) |
108 | end |
109 | |
110 | def lock_duration(time) |
111 | ((time - locked_at) / 60).to_i unless locked_at.nil? |
112 | end |
113 | |
114 | def unlock |
115 | update_attribute(:locked_at, nil) |
116 | end |
117 | |
118 | def locked?(comparison_time) |
119 | locked_at + LOCKING_PERIOD > comparison_time unless locked_at.nil? |
120 | end |
121 | |
122 | def to_param |
123 | name.as_utf8 |
124 | end |
125 | |
126 | private |
127 | |
128 | def continous_revision?(time, author) |
129 | (current_revision.author == author) && (revised_at + 30.minutes > time) |
130 | end |
131 | |
132 | # Forward method calls to the current revision, so the page responds to all revision calls |
133 | def method_missing(method_id, *args, &block) |
134 | method_name = method_id.to_s |
135 | # Perform a hand-off to AR::Base#method_missing |
136 | if @attributes.include?(method_name) or md = /(=|\?|_before_type_cast)$/.match(method_name) |
137 | super(method_id, *args, &block) |
138 | else |
139 | current_revision.send(method_id) |
140 | end |
141 | end |
142 | |
143 | # 2011-03-12 (ADH): Patch for Textile based wiki code. |
144 | # |
145 | # Some people use Textile links to refer to pages within the Wiki, perhaps |
146 | # because they're unaware of the "[[Page name|Visible text]]" alias syntax. |
147 | # This means that the Wiki may think a page is an orphan, with no links to |
148 | # it, because there *are* links but they're in Textile format, not in Wiki |
149 | # reference format. |
150 | # |
151 | # This method takes a string and returns a reasonably robustly processed |
152 | # equivalent which has any Textile links which look like inter-page |
153 | # references replaced by Wiki syntax equivalents on the "[[link|alias]]" |
154 | # form. Typically, you pass the body text for entire revision in here. |
155 | # |
156 | # Heavily obfuscated Textile links, e.g. those which might specify an |
157 | # absolute URL path from the document root or even including a host name |
158 | # but still ultimately point to another page within the Wiki, will not be |
159 | # replaced. So if users try hard enough, they can still break things! |
160 | # |
161 | unless defined? TEXTILE_LINK_PATTERN |
162 | |
163 | # In TEXTILE_ALIAS_PATTERN_START, we want it anchored to the start of a |
164 | # line but a <textarea> uses "\r\n". The regexp fails if we try to use |
165 | # "^" to anchor to start-of-line. Instead, match "\r\n" literally. |
166 | # |
167 | # In TEXTILE_LINK_PATTERN, after the ":" any non-white space character is |
168 | # allowed. Trailing punctuation at the end of the link ('"foo":bar. ') is |
169 | # stripped if necessary later. A '"' is not allowed immediately after the |
170 | # ':' either, since Textile doesn't allow it and one or two sequences in |
171 | # the ROOL Wiki can lead to disaster otherwise, such as '"#",":"'. In a |
172 | # similar vein, a vertical bar is also ignored as this is a Textile table |
173 | # cell separator and can't be used in named link references either. |
174 | |
175 | TEXTILE_LINK_PATTERN = /\[?"([^\s].*?)":([^\s\"\|]+)\]?/ |
176 | TEXTILE_LINK_TRIM_PATTERN = /[^\w]+$/ |
177 | TEXTILE_ALIAS_PATTERN_START = /\r\n\[/.source |
178 | TEXTILE_ALIAS_PATTERN_END = /\]([^\s]+)/.source |
179 | |
180 | ROOL_OLD_WIKI_PATH_PREFIX = '/wiki/documentation/pages/' |
181 | end |
182 | # |
183 | def patch_interpage_textile_links( str ) |
184 | |
185 | # Changes will be made to "sub_str". The original is used for searches. |
186 | |
187 | sub_str = str.dup |
188 | |
189 | # Textile links may have the link inline, or refer to a link later in the |
190 | # text using an alias. We record aliases and strip them out after the main |
191 | # substitutions are done. We can't strip them out as we do the main |
192 | # substitutions because several different Textile links may refer to the |
193 | # same alias, even if other parts of that link are different. |
194 | |
195 | alias_matches = [] |
196 | |
197 | # Scan the string for Textile links. In each match, we get the visible text |
198 | # in the first parameter, link or alias in the second parameter and the |
199 | # whole matched string set in "$&". |
200 | |
201 | str.scan( TEXTILE_LINK_PATTERN ) do | visible_text, link_or_alias | |
202 | |
203 | whole_link = $& |
204 | |
205 | # Patch the search: The link or alias has to be allowed to include all |
206 | # kinds of characters, because they do! Stars, hyphens, underscores and |
207 | # various other things. However we find in practice that Textile can |
208 | # understand when a punctuation character occurs at the very end of a |
209 | # link (it's followed by white space) and so this isn't considered part |
210 | # of that link (even for ".", which might otherwise be legitimately |
211 | # present for e.g. filename extensions). |
212 | # |
213 | # Accordingly, trim any non-alphabetic, non-numeric characters off the |
214 | # end of the whole found piece of text and the link or alias text. |
215 | |
216 | whole_link.gsub!( TEXTILE_LINK_TRIM_PATTERN, '' ) |
217 | link_or_alias.gsub!( TEXTILE_LINK_TRIM_PATTERN, '' ) |
218 | |
219 | # Generate a regular expression which matches an alias definition. If |
220 | # we find it, then this link used an alias by definition; else the URL |
221 | # was inline. |
222 | |
223 | alias_regexp = Regexp.new( "#{ TEXTILE_ALIAS_PATTERN_START }#{ Regexp.escape( link_or_alias ) }#{ TEXTILE_ALIAS_PATTERN_END }" ) |
224 | alias_match = str.match( alias_regexp ) |
225 | |
226 | # If an alias definition is found, index 1 will hold the URI part. |
227 | |
228 | if ( alias_match.nil? ) |
229 | link = link_or_alias |
230 | else |
231 | link = alias_match[ 1 ] |
232 | end |
233 | |
234 | # Special case: If we have a known absolute URL prefix on the link, |
235 | # strip it off. This is in case someone's daft enough (and it's seen on |
236 | # the ROOL wiki) to encode a Textile format link with a URL path from |
237 | # the root right back down to a Wiki page - for some reason. |
238 | # |
239 | # This is very much ROOL specific since the URL path prefix in question |
240 | # used here is from the old ROOL I2-based Wiki. |
241 | |
242 | if ( link.index( ROOL_OLD_WIKI_PATH_PREFIX ) == 0 ) |
243 | link = link[ ROOL_OLD_WIKI_PATH_PREFIX.length .. -1 ] |
244 | end |
245 | |
246 | # If there's a "/" in the link, it can't be an in-Wiki reference or |
247 | # alias. Obfuscated in-Wiki references are possible ("/wiki_root/page", |
248 | # that kind of thing) but we don't try to catch everything. If the user |
249 | # tries hard enough they can defeat this code. We'd have to do something |
250 | # complex with URI canonicalisation and the routing table to see if the |
251 | # URI could possibly refer to the Wiki. It's really not worth the effort |
252 | # and the performance impact to try that hard to avoid page references |
253 | # that may lead to pages being declared as orphans, even though there is |
254 | # technically an obscure format of reference elsewhere within the Wiki. |
255 | # |
256 | # A further complication is anchor references. In the ROOL Wiki, Textile |
257 | # is sometimes used to link to anchors within pages but in most cases |
258 | # there's also a higher level link to wider page too. Since that wider |
259 | # link will be Wiki-fied, the page won't be an orphan; so leave the |
260 | # anchor-based links in Textile format (there's no equivalent Wiki |
261 | # syntax for them). |
262 | |
263 | next if ( link.include?( '/' ) || link.include?( '#' ) ) |
264 | |
265 | # A "+" in a Textile link from I2 translates to a space in reality. After |
266 | # that, hex sequences of the form "%XY" should be unescaped to arrive at |
267 | # something Instiki will recognise as a Wiki page title. |
268 | # |
269 | # There are more esoteric forms such as "(foo). Text" which assigns class |
270 | # "foo" to the <a> element, but we don't support those here. |
271 | |
272 | link.gsub!( '+', ' ' ) |
273 | link = CGI::unescape( link ) |
274 | |
275 | # Substitute any matching Textile links with a Wiki link equivalent. |
276 | |
277 | sub_str.gsub!( whole_link, "[[#{ link }|#{ visible_text }]]" ) |
278 | |
279 | # Remember alias definitions for removal later. |
280 | |
281 | alias_matches.push( alias_match[ 0 ] ) unless alias_match.nil? |
282 | |
283 | end |
284 | |
285 | # More slowness... Must now remove any referenced links too. Maintain |
286 | # the "\r\n" included by the regular expression so that we don't |
287 | # accidentally end up collapsing non-replaced link references against |
288 | # textile output above, which "modern" Textile doesn't like. |
289 | |
290 | alias_matches.each do | alias_match | |
291 | sub_str.gsub!( alias_match, "\r\n" ) |
292 | end |
293 | |
294 | return sub_str |
295 | end |
296 | end |