Soks 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. data/README.txt +5 -4
  2. data/bin/soks-create-wiki.rb +153 -19
  3. data/contrib/easyprompt.rb +58 -0
  4. data/contrib/easyprompt_licence.txt +504 -0
  5. data/contrib/redcloth-2.0.11.rb +3 -1
  6. data/lib/authenticators.rb +18 -2
  7. data/lib/soks-helpers.rb +207 -157
  8. data/lib/soks-model.rb +131 -114
  9. data/lib/soks-servlet.rb +54 -35
  10. data/lib/soks-storage.rb +134 -0
  11. data/lib/soks-upgrade-0.0.2.rb +70 -0
  12. data/lib/soks-utils.rb +129 -19
  13. data/lib/soks-view.rb +136 -62
  14. data/lib/soks.rb +3 -1
  15. data/{template → templates/default}/attachment/logo.png +0 -0
  16. data/templates/default/attachment/logo.tiff +0 -0
  17. data/templates/default/attachment/newpage.js +41 -0
  18. data/templates/default/attachment/print_stylesheet.css +7 -0
  19. data/templates/default/attachment/rss.png +0 -0
  20. data/{template → templates/default}/attachment/stylesheet.css +44 -17
  21. data/templates/default/banned_titles.txt +31 -0
  22. data/templates/default/content/Bug%3A%20In%20a%20list%20of%20links%2C%20the%20last%20link%20is%20sometimes%20not%20linked.textile +10 -0
  23. data/templates/default/content/Bug%3A%20Symbols%20are%20not%20always%20correctly%20rendered%20in%20html.textile +3 -0
  24. data/templates/default/content/Bug%3A%20Uploads%20are%20not%20password%20protected.textile +3 -0
  25. data/templates/default/content/How%20to%20administrate%20this%20wiki.textile +62 -0
  26. data/templates/default/content/How%20to%20change%20the%20way%20this%20wiki%20looks.textile +30 -0
  27. data/templates/default/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +60 -0
  28. data/{template → templates/default}/content/How%20to%20hack%20soks.textile +3 -2
  29. data/{template → templates/default}/content/How%20to%20import%20a%20site%20from%20instiki.textile +1 -1
  30. data/templates/default/content/How%20to%20use%20this%20wiki.textile +27 -0
  31. data/templates/default/content/List%20of%20changes.textile +35 -0
  32. data/{template → templates/default}/content/Picture%20of%20a%20pair%20of%20soks.textile +0 -0
  33. data/{template → templates/default}/content/Soks%20Licence.textile +0 -0
  34. data/templates/default/content/home%20page.textile +17 -0
  35. data/templates/default/start.rb +94 -0
  36. data/templates/default/version.txt +1 -0
  37. data/{template → templates/default}/views/Page_content.rhtml +0 -0
  38. data/templates/default/views/Page_edit.rhtml +61 -0
  39. data/templates/default/views/Page_meta.rhtml +40 -0
  40. data/templates/default/views/Page_print.rhtml +6 -0
  41. data/templates/default/views/Page_revisions.rhtml +19 -0
  42. data/templates/default/views/Page_rss.rhtml +55 -0
  43. data/{template → templates/default}/views/Page_search_results.rhtml +1 -1
  44. data/templates/default/views/Page_view.rhtml +4 -0
  45. data/templates/default/views/UploadPage_edit.rhtml +38 -0
  46. data/templates/default/views/frame.rhtml +41 -0
  47. data/templates/default/views/messages.yaml +6 -0
  48. data/templates/instiki/attachment/header_backdrop.png +0 -0
  49. data/templates/instiki/attachment/instiki_style_sheet.css +199 -0
  50. data/templates/instiki/attachment/logo.tiff +0 -0
  51. data/templates/instiki/attachment/logotext.png +0 -0
  52. data/templates/instiki/attachment/newpage.js +41 -0
  53. data/templates/instiki/attachment/rss.png +0 -0
  54. data/templates/instiki/banned_titles.txt +31 -0
  55. data/templates/instiki/content/AutomaticSummary.textile +24 -0
  56. data/templates/instiki/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +60 -0
  57. data/templates/instiki/content/How%20to%20hack%20soks.textile +61 -0
  58. data/templates/instiki/content/How%20to%20import%20a%20site%20from%20instiki.textile +13 -0
  59. data/{template → templates/instiki}/content/Improving%20the%20style%20of%20this%20wiki.textile +2 -2
  60. data/templates/instiki/content/Known%20bugs.textile +8 -0
  61. data/templates/instiki/content/List%20of%20changes.textile +34 -0
  62. data/templates/instiki/content/Picture%20of%20a%20pair%20of%20soks.textile +1 -0
  63. data/templates/instiki/content/Pointers%20on%20adjusting%20the%20settings.textile +62 -0
  64. data/templates/instiki/content/Pointers%20on%20how%20to%20use%20this%20wiki.textile +27 -0
  65. data/templates/instiki/content/Recent%20Blog%20Entries.textile +3 -0
  66. data/templates/instiki/content/Recent%20Changes%20to%20This%20Site.textile +48 -0
  67. data/templates/instiki/content/Site%20Index.textile +16 -0
  68. data/templates/instiki/content/Soks%20Licence.textile +64 -0
  69. data/{template → templates/instiki}/content/home%20page.textile +9 -4
  70. data/templates/instiki/start.rb +85 -0
  71. data/templates/instiki/version.txt +1 -0
  72. data/templates/instiki/views/Page_content.rhtml +1 -0
  73. data/templates/instiki/views/Page_edit.rhtml +8 -0
  74. data/templates/instiki/views/Page_meta.rhtml +34 -0
  75. data/templates/instiki/views/Page_print.rhtml +6 -0
  76. data/templates/instiki/views/Page_revisions.rhtml +17 -0
  77. data/templates/instiki/views/Page_rss.rhtml +55 -0
  78. data/templates/instiki/views/Page_search_results.rhtml +18 -0
  79. data/templates/instiki/views/Page_view.rhtml +2 -0
  80. data/templates/instiki/views/UploadPage_edit.rhtml +16 -0
  81. data/templates/instiki/views/frame.rhtml +90 -0
  82. data/templates/instiki/views/messages.yaml +6 -0
  83. data/templates/rails/attachment/2colheader.css +77 -0
  84. data/templates/rails/attachment/basics.css +98 -0
  85. data/templates/rails/attachment/header_backdrop.png +0 -0
  86. data/templates/rails/attachment/logo.tiff +0 -0
  87. data/templates/rails/attachment/logotext.png +0 -0
  88. data/templates/rails/attachment/newpage.js +41 -0
  89. data/templates/rails/attachment/rss.png +0 -0
  90. data/templates/rails/banned_titles.txt +31 -0
  91. data/templates/rails/content/AutomaticSummary.textile +24 -0
  92. data/templates/rails/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +60 -0
  93. data/templates/rails/content/How%20to%20hack%20soks.textile +61 -0
  94. data/templates/rails/content/How%20to%20import%20a%20site%20from%20instiki.textile +13 -0
  95. data/templates/rails/content/Improving%20the%20style%20of%20this%20wiki.textile +30 -0
  96. data/templates/rails/content/Known%20bugs.textile +8 -0
  97. data/templates/rails/content/List%20of%20changes.textile +34 -0
  98. data/templates/rails/content/Picture%20of%20a%20pair%20of%20soks.textile +1 -0
  99. data/templates/rails/content/Pointers%20on%20adjusting%20the%20settings.textile +62 -0
  100. data/templates/rails/content/Pointers%20on%20how%20to%20use%20this%20wiki.textile +27 -0
  101. data/templates/rails/content/Recent%20Blog%20Entries.textile +3 -0
  102. data/templates/rails/content/Recent%20Changes%20to%20This%20Site.textile +48 -0
  103. data/templates/rails/content/Site%20Index.textile +16 -0
  104. data/templates/rails/content/Soks%20Licence.textile +64 -0
  105. data/templates/rails/content/home%20page.textile +23 -0
  106. data/templates/rails/start.rb +85 -0
  107. data/templates/rails/version.txt +1 -0
  108. data/templates/rails/views/Page_content.rhtml +1 -0
  109. data/templates/rails/views/Page_edit.rhtml +61 -0
  110. data/templates/rails/views/Page_meta.rhtml +38 -0
  111. data/templates/rails/views/Page_print.rhtml +6 -0
  112. data/templates/rails/views/Page_revisions.rhtml +19 -0
  113. data/templates/rails/views/Page_rss.rhtml +55 -0
  114. data/templates/rails/views/Page_search_results.rhtml +19 -0
  115. data/templates/rails/views/Page_view.rhtml +3 -0
  116. data/templates/rails/views/UploadPage_edit.rhtml +38 -0
  117. data/templates/rails/views/frame.rhtml +60 -0
  118. data/templates/rails/views/messages.yaml +6 -0
  119. metadata +122 -28
  120. data/template/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +0 -5
  121. data/template/content/Pointers%20on%20adjusting%20the%20settings.textile +0 -39
  122. data/template/content/Pointers%20on%20how%20to%20use%20this%20wiki.textile +0 -21
  123. data/template/content/Recent%20Changes%20to%20This%20Site.textile +0 -203
  124. data/template/start.rb +0 -74
  125. data/template/views/AttachmentPage_edit.rhtml +0 -36
  126. data/template/views/ImagePage_edit.rhtml +0 -36
  127. data/template/views/Page_edit.rhtml +0 -34
  128. data/template/views/Page_print.rhtml +0 -5
  129. data/template/views/Page_revisions.rhtml +0 -18
  130. data/template/views/Page_rss.rhtml +0 -34
  131. data/template/views/Page_view.rhtml +0 -3
  132. data/template/views/frame.rhtml +0 -34
@@ -0,0 +1,134 @@
1
+ module WikiFlatFileStore
2
+
3
+ CONTENT_EXTENSION = '.textile'
4
+ REVISIONS_EXTENSION = '.yaml'
5
+ DEFAULT_AUTHOR = 'AutomaticImport'
6
+
7
+ def save( page )
8
+ add_page_to_index( page )
9
+ save_content( page )
10
+ save_revisions( page )
11
+ end
12
+
13
+ def load_all_pages
14
+ move_files_if_names_are_not_url_encoded
15
+ pages_on_disk = Dir[ File.join( @folder, "*#{CONTENT_EXTENSION}" ) ].map { |filename| page_name_for( filename )}
16
+ pages_in_memory = @pages.values.map { |page| page && page.name }
17
+ ( pages_in_memory.compact | pages_on_disk ).each do |pagename|
18
+ page = page_named( pagename )
19
+
20
+ if page && !File.exists?( filename_for_content( pagename ) )
21
+ @pages[ page.name.downcase ] = nil
22
+ notify :page_revised, page, page.revisions.last
23
+ notify :page_deleted, page, page.revisions.last
24
+ next
25
+ end
26
+ unless page
27
+ page = load_page( filename_for_content( pagename ) )
28
+ notify :page_revised, page, page.revisions.last
29
+ notify :page_created, page, page.revisions.last
30
+ next
31
+ end
32
+ if check_disk_for_updated_page pagename
33
+ page = page_named( pagename )
34
+ notify :page_revised, page, page.revisions.last
35
+ end
36
+ end
37
+ end
38
+
39
+ def load_page( filename )
40
+ page = new_page( page_name_for( filename ) )
41
+ page.revisions = load_revisions( page )
42
+ content = load_content( page )
43
+
44
+ if page_named( page.name ) # Save having to find this out again
45
+ page.inserted_into = page_named( page.name ).inserted_into
46
+ end
47
+
48
+ if page.revisions.empty? # There is a textile file but no array file
49
+ page.revise(content, DEFAULT_AUTHOR )
50
+ save_revisions( page )
51
+ elsif content_newer_than_revisions?( page ) # The textile file has been modified, but the array file has not been updated to match
52
+ page.content = reconstruct_content_from_revisions( page.revisions )
53
+ page.revise( content, DEFAULT_AUTHOR )
54
+ save_revisions( page )
55
+ else # The textile file and the array file are in sync.
56
+ page.content = content
57
+ end
58
+
59
+ add_page_to_index( page )
60
+ page
61
+ end
62
+
63
+ def load_content( page )
64
+ IO.readlines( filename_for_content( page.name ) ).join
65
+ end
66
+
67
+ def load_revisions( page )
68
+ return [] unless File.exists?( filename_for_revisions( page.name ) )
69
+ revisions = []
70
+ File.open( filename_for_revisions( page.name ) ) { |file|
71
+ YAML.each_document( file ) { |array|
72
+ revisions[ array[0] ] = Revision.new( page, *array )
73
+ }
74
+ }
75
+ revisions.each_with_index { |r,i| $stderr.puts "#{page.name} missing revision #{i}" unless r }
76
+ revisions
77
+ end
78
+
79
+ def save_content( page )
80
+ File.open(filename_for_content( page.name ), 'w' ) { |file| file.puts page.content }
81
+ end
82
+
83
+ # Appends the last revision onto the yaml file
84
+ def save_revisions( page )
85
+ $stderr.puts "Saving revisions for #{page.name}"
86
+ File.open(filename_for_revisions( page.name ), 'a' ) { |file|
87
+ YAML.dump( page.revisions.last.to_a, file )
88
+ file.puts
89
+ }
90
+ end
91
+
92
+ def check_disk_for_updated_page( pagename )
93
+ return unless $SETTINGS[:check_files_every] # We don't care about file changes
94
+ filename = filename_for_content( pagename )
95
+ return nil unless File.exists?( filename ) # File doesn't exist on disk
96
+ return load_page( filename ) unless page_named pagename # File is new on the disk, but not yet in memory
97
+ return load_page( filename ) if ( page_named(pagename).revised_on < File.ctime( filename ) ) # File is newer on disk
98
+ return nil
99
+ end
100
+
101
+ def content_newer_than_revisions?( page )
102
+ page_file = filename_for_content page.name
103
+ revision_file = filename_for_revisions page.name
104
+ File.ctime(page_file) > File.ctime(revision_file)
105
+ end
106
+
107
+ def reconstruct_content_from_revisions( revisions )
108
+ content = []
109
+ revisions.each { |revision| content = Diff::LCS.patch( content, revision.changes, :patch ) }
110
+ content.join("\n")
111
+ end
112
+
113
+ def move_files_if_names_are_not_url_encoded
114
+ Dir[ File.join( @folder, "*#{CONTENT_EXTENSION}" ) ].each do |filename|
115
+ basename = File.basename( filename, '.*')
116
+ next if basename.url_decode.url_encode == basename # All ok, so no worry
117
+ new_name = File.join( File.dirname(filename), basename.url_decode.url_encode) + File.extname( filename)
118
+ File.rename(filename, new_name )
119
+ end
120
+ end
121
+
122
+ def page_name_for( filename )
123
+ File.basename( filename, '.*').url_decode
124
+ end
125
+
126
+ def filename_for_content( pagename )
127
+ File.join( @folder, "#{pagename.url_encode}#{CONTENT_EXTENSION}" )
128
+ end
129
+
130
+ def filename_for_revisions( pagename )
131
+ File.join( @folder, "#{pagename.url_encode}#{REVISIONS_EXTENSION}" )
132
+ end
133
+ end
134
+
@@ -0,0 +1,70 @@
1
+ require 'soks-utils'
2
+ require 'yaml'
3
+
4
+ # This is the definition of Revision from v-0-0-2.
5
+ # Much smaller than the current definition. sigh.
6
+ class Revision
7
+ attr_reader :number, :changes, :created_at, :author
8
+
9
+ def initialize( number, changes, author )
10
+ @number, @changes, @author = number, changes, author
11
+ @created_at = Time.now
12
+ end
13
+
14
+ def content( page )
15
+ page.revision( @number + 1 ) ? page.revision( @number + 1 ).previous_content( page ) : page.content
16
+ end
17
+
18
+ def previous_content( page )
19
+ content( page ).split("\n").unpatch!(@changes).join("\n")
20
+ end
21
+ end
22
+
23
+ class SoksUpgrade
24
+
25
+ def load_old_revisions( filename )
26
+ File.open( filename ) { |file| return Marshal.load( file ) }
27
+ end
28
+
29
+ def save_new_revisions( old_filename, revisions )
30
+ File.open(new_filename_for_old( old_filename ), 'w' ) do |file|
31
+ revisions.each do |revision|
32
+ YAML.dump( [revision.number, revision.changes, revision.author, revision.created_at ] , file )
33
+ file.puts
34
+ end
35
+ end
36
+ end
37
+
38
+ def new_filename_for_old( old_filename )
39
+ basename = File.basename( old_filename, '.*')
40
+ new_extension = '.yaml'
41
+ File.join( File.dirname(old_filename), basename ) + new_extension
42
+ end
43
+
44
+ def upgrade_revisions( directory )
45
+ search = File.join( directory,'content', "*.marshal" )
46
+ Dir[ search ].each do |filename|
47
+ puts "Upgrading #{filename}"
48
+ save_new_revisions( filename, load_old_revisions( filename ))
49
+ File.delete filename
50
+ end
51
+ end
52
+
53
+ def upgrade_textile( filename )
54
+ textile = IO.readlines( filename ).join
55
+ textile.gsub!(/\[\[\s*(.*?)\s*(|:\s*(.*?)\s*)\]\]/) do |m|
56
+ title, page = $1, $3
57
+ page ? "[[ #{title} => #{page} ]]" : "[[ #{title} ]]"
58
+ end
59
+ File.open( filename, 'w' ) { |f| f.puts textile }
60
+ end
61
+
62
+ def upgrade_content( directory )
63
+ search = File.join( directory,'content', "*.textile" )
64
+ Dir[ search ].each do |filename|
65
+ puts "Upgrading #{filename}"
66
+ upgrade_textile( filename )
67
+ end
68
+ end
69
+
70
+ end
data/lib/soks-utils.rb CHANGED
@@ -1,32 +1,76 @@
1
+ # This is a bit like observable, but for events
2
+ module Notify
3
+
4
+ # Will notify in a separate low priority thread
5
+ def watch_for( *events , &action_block )
6
+ self.event_queue.watch_for( events, action_block )
7
+ end
8
+
9
+ # Will notify in the running high priority thread (ie will block response to user)
10
+ def watch_attentively_for( *events, &action_block )
11
+ self.event_queue.watch_attentively_for( events, action_block )
12
+ end
13
+
14
+ def notify( event, *messages)
15
+ self.event_queue.event( event, messages )
16
+ end
17
+
18
+ def event_queue
19
+ @event_queue ||= EventQueue.new
20
+ end
21
+ end
22
+
1
23
  class EventQueue
2
24
 
3
25
  def initialize
4
26
  @queue = Queue.new
5
- Thread.new do
6
- loop do
7
- check_for_events
8
- end
9
- end.priority = -1
27
+ start_thread
10
28
  end
11
29
 
12
30
  def event( event, messages )
31
+ check_thread_ok
13
32
  @queue.enq [ event, messages ]
33
+ notify_attentive_watchers( event, *messages )
14
34
  end
15
35
 
36
+ # Will call the action_block lazily
16
37
  def watch_for( events , action_block )
17
38
  events.each { |event| watchers_for(event) << action_block }
18
39
  end
19
40
 
41
+ # Will call the action_block imediately
42
+ def watch_attentively_for( events, action_block )
43
+ events.each { |event| attentive_watchers_for(event) << action_block }
44
+ end
45
+
20
46
  private
21
47
 
48
+ def check_thread_ok
49
+ start_thread unless @thread && @thread.alive?
50
+ end
51
+
52
+ def start_thread
53
+ @thread = Thread.new do
54
+ loop do
55
+ check_for_events
56
+ end
57
+ end
58
+ @thread.priority = -1
59
+ end
60
+
22
61
  def check_for_events
23
62
  event, messages = @queue.deq
24
63
  notify( event, *messages )
25
64
  end
26
65
 
27
66
  def notify( event, *messages)
28
- watchers_for( event ).each { |action_block|
29
- action_block.call(event, *messages)
67
+ watchers_for( event ).each { |action_block|
68
+ begin
69
+ action_block.call(event, *messages)
70
+ rescue StandardError => err
71
+ $stderr.puts "ERROR #{err}: #{event} - #{messages.join(' ')}"
72
+ err.backtrace.each { |s| $stderr.puts s }
73
+ end
30
74
  }
31
75
  end
32
76
 
@@ -37,31 +81,37 @@ class EventQueue
37
81
  def watchers
38
82
  @watchers ||= {}
39
83
  end
40
- end
41
-
42
- # This is a bit like observable, but for events
43
- module Notify
44
84
 
45
- def watch_for( *events , &action_block )
46
- self.event_queue.watch_for( events, action_block )
85
+ def notify_attentive_watchers( event, *messages )
86
+ attentive_watchers_for( event ).each { |action_block|
87
+ begin
88
+ action_block.call(event, *messages)
89
+ rescue StandardError => err
90
+ $stderr.puts "ERROR #{err}: #{event} - #{messages.join(' ')}"
91
+ err.backtrace.each { |s| $stderr.puts s }
92
+ end
93
+ }
47
94
  end
48
95
 
49
- def notify( event, *messages)
50
- self.event_queue.event( event, messages )
96
+ def attentive_watchers_for( event )
97
+ attentive_watchers[ event ] ||= []
51
98
  end
52
99
 
53
- def event_queue
54
- @event_queue ||= EventQueue.new
100
+ def attentive_watchers
101
+ @attentive_watchers ||= {}
55
102
  end
103
+
56
104
  end
57
105
 
106
+
107
+
58
108
  class String
59
109
  # Return the left bit of a string e.g. "String".left(2) => "St"
60
110
  def left( length ) self.slice( 0, length ) end
61
111
 
62
112
  # Encode the string so it can be used in urls (code coppied from CGI)
63
113
  def url_encode
64
- self.gsub(/([^a-zA-Z0-9_.-]+)/n) do
114
+ self.gsub(/([^a-zA-Z0-9]+)/n) do
65
115
  '%' + $1.unpack('H2' * $1.size).join('%').upcase
66
116
  end.tr(' ', '+')
67
117
  end
@@ -77,4 +127,64 @@ class String
77
127
  def first_lines( lines = 1 )
78
128
  self.split("\n")[0,lines].join("\n")
79
129
  end
80
- end
130
+
131
+ def close_unmatched_html
132
+ start_tags = self.scan(/<(\w+)[^>\/]*?(?!\/)>/)
133
+ end_tags = self.scan(/<\/(\w+)[^>\/]*?>/)
134
+ return self if start_tags.size == end_tags.size
135
+ missing_tags = start_tags - end_tags
136
+ text = self.dup
137
+ missing_tags.each do |tag|
138
+ text << "</#{tag[0]}>"
139
+ end
140
+ text
141
+ end
142
+ end
143
+
144
+ class FiniteUniqueList
145
+ include Enumerable
146
+
147
+ attr_accessor :max_size
148
+
149
+ def initialize( max_size = nil, reverse = false, sort_by = nil )
150
+ @max_size = max_size
151
+ @list = Array.new
152
+ @sort_by = sort_by
153
+ @reverse = reverse
154
+ end
155
+
156
+ def add( item )
157
+ remove( item )
158
+ @list << item
159
+ sort_items
160
+ remove_excess_items
161
+ end
162
+
163
+ def remove( item )
164
+ @list.delete( item )
165
+ end
166
+
167
+ def each
168
+ if @reverse
169
+ @list.reverse_each { |item| yield item }
170
+ else
171
+ @list.each { |item| yield item }
172
+ end
173
+ end
174
+
175
+ def empty?; @list.empty? end
176
+
177
+ private
178
+
179
+ def remove_excess_items
180
+ return unless @max_size
181
+ while @list.size > @max_size
182
+ @list.shift
183
+ end
184
+ end
185
+
186
+ def sort_items
187
+ return unless @sort_by
188
+ @list = @list.sort_by { |item| item.send( @sort_by ) }
189
+ end
190
+ end
data/lib/soks-view.rb CHANGED
@@ -15,13 +15,11 @@ class Links
15
15
  linksto = Array.new
16
16
  @links.each do | pagefrom, pagesto |
17
17
  next if pagefrom == thispage
18
- unless [ "site index", "recent changes to this site" ].include? pagefrom.name.downcase
19
- if pagesto.include? thispage
20
- linksto << pagefrom
21
- end
22
- end
18
+ next if [ "site index", "recent changes to this site" ].include? pagefrom.name.downcase
19
+ next if linksto.include? pagefrom
20
+ linksto << pagefrom if pagesto.include? thispage
23
21
  end
24
- return linksto.uniq.sort
22
+ return linksto.sort
25
23
  end
26
24
  end
27
25
 
@@ -30,8 +28,8 @@ end
30
28
  # the first thing it can, rather than skipping a shorter match to enable
31
29
  # a longer one that starts at a later point in the text
32
30
  class RollingMatch
33
- WORD_REGEX = /^\w*/
34
- SPACE_REGEX = /^\W*/
31
+ WORD_REGEX = /\w*/
32
+ SPACE_REGEX = /\W*/
35
33
  IGNORE_CASE = true
36
34
 
37
35
  def initialize
@@ -117,29 +115,32 @@ if RedCloth::VERSION == '2.0.11'
117
115
 
118
116
  @urlrefs = {}
119
117
  @shelf = []
120
- @internal_links_from_page = [] # NEW
118
+ @internal_links_from_page = [] # NEW
121
119
 
122
- insert_sub_strings text # NEW
123
-
124
- incoming_entities text
125
- ## encode_entities text
126
- ## fix_entities text
127
- clean_white_space text
128
-
129
- get_refs text
120
+ insert_sub_strings text # NEW
121
+
122
+ shelve_divs_and_spans text # NEW
123
+
124
+ clean_white_space text
130
125
 
131
126
  no_textile text
132
127
 
133
128
  pre_list = rip_offtags text # NEW wasteful to duplicate, but reduces overloading
134
129
  hide_textile_links text # NEW
135
130
  hide_textile_image_tags text # NEW
136
- inline_soks_external_link text # NEW
137
131
  inline_soks_bracketed_link text # NEW
132
+ inline_soks_external_link text # NEW
138
133
  inline_soks_automatic_link text # NEW
139
134
  @wiki.links.set_links_from( @page, @internal_links_from_page ) # NEW
140
135
  unhide_textile text # NEW
141
136
  smooth_offtags text, pre_list # NEW wasteful to duplicate
142
137
 
138
+ incoming_entities text
139
+ ## encode_entities text
140
+ ## fix_entities text
141
+
142
+ get_refs text
143
+
143
144
  inline text
144
145
 
145
146
  unless @lite
@@ -153,8 +154,12 @@ if RedCloth::VERSION == '2.0.11'
153
154
  text.gsub!( /x%x%/, '&#38;' )
154
155
  text.gsub!( /<br \/>/, "<br />\n" )
155
156
  text.strip!
157
+
158
+ clean_html text if $SETTINGS[:strict_html_removal]
159
+
156
160
  text
157
161
  end
162
+
158
163
  end
159
164
 
160
165
  if RedCloth::VERSION == '3.0.1'
@@ -173,7 +178,7 @@ if RedCloth::VERSION == '3.0.1'
173
178
  markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
174
179
  :block_markdown_bq, :block_markdown_lists,
175
180
  :inline_markdown_reflink, :inline_markdown_link]
176
- soks_rules = [ :hide_textile_links, :hide_textile_image_tags, :inline_soks_external_link, :inline_soks_bracketed_link, :inline_soks_automatic_link, :unhide_textile ]
181
+ soks_rules = [ :hide_textile_links, :hide_textile_image_tags, :inline_soks_bracketed_link, :inline_soks_external_link, :inline_soks_automatic_link, :unhide_textile ]
177
182
  @rules = rules.collect do |rule|
178
183
  case rule
179
184
  when :markdown
@@ -188,7 +193,7 @@ if RedCloth::VERSION == '3.0.1'
188
193
  end.flatten
189
194
 
190
195
  # insert sub pages
191
- insert_sub_strings text
196
+ insert_sub_strings text
192
197
 
193
198
  # standard clean up
194
199
  incoming_entities text
@@ -208,11 +213,81 @@ if RedCloth::VERSION == '3.0.1'
208
213
  text.gsub!( /<\/?notextile>/, '' )
209
214
  text.gsub!( /x%x%/, '&#38;' )
210
215
  text.strip!
216
+
217
+ clean_html text
218
+
211
219
  text
212
220
  end
213
221
  end
214
222
 
215
223
  private
224
+
225
+ def shelve_divs_and_spans( text )
226
+ text.gsub!(/<\/+(div|span).*?>/) { |m| shelve m }
227
+ end
228
+
229
+ ## Dictionary describing allowable HTML
230
+ ## tags and attributes.
231
+ BASIC_TAGS = {
232
+ 'a' => ['href', 'title'],
233
+ 'img' => ['src', 'alt', 'title'],
234
+ 'br' => [],
235
+ 'i' => nil,
236
+ 'u' => nil,
237
+ 'b' => nil,
238
+ 'pre' => nil,
239
+ 'kbd' => nil,
240
+ 'code' => ['lang'],
241
+ 'cite' => nil,
242
+ 'strong' => nil,
243
+ 'em' => nil,
244
+ 'ins' => nil,
245
+ 'sup' => nil,
246
+ 'sub' => nil,
247
+ 'del' => nil,
248
+ 'table' => [ 'class', 'style' ],
249
+ 'tr' => [ 'class', 'style' ],
250
+ 'td' => [ 'class', 'style' ],
251
+ 'th' => [ 'class', 'style' ],
252
+ 'ol' => nil,
253
+ 'ul' => nil,
254
+ 'li' => nil,
255
+ 'p' => [ 'class', 'style' ],
256
+ 'h1' => nil,
257
+ 'h2' => nil,
258
+ 'h3' => nil,
259
+ 'h4' => nil,
260
+ 'h5' => nil,
261
+ 'h6' => nil,
262
+ 'blockquote' => ['cite'],
263
+ 'div' => [ 'class', 'id', 'style' ],
264
+ 'span' => [ 'class', 'id','style'],
265
+ }
266
+
267
+ ## Method which cleans the String of HTML tags
268
+ ## and attributes outside of the allowed list.
269
+ def clean_html( text, tags = BASIC_TAGS )
270
+ text.gsub!( /<(\/*)(\w+)([^>\/]*?)( *\/*)>/ ) do
271
+ raw = $~
272
+ tag = raw[2].downcase
273
+ if tags.has_key? tag
274
+ pcs = [tag]
275
+ tags[tag].each do |prop|
276
+ ['"', "'", ''].each do |q|
277
+ q2 = ( q != '' ? q : '\s' )
278
+ if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
279
+ pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
280
+ break
281
+ end
282
+ end
283
+ end if tags[tag]
284
+ "<#{raw[1]}#{pcs.join " "}#{raw[4]}>"
285
+ else
286
+ " "
287
+ end
288
+ end
289
+ text
290
+ end
216
291
 
217
292
  def insert_sub_strings( text, count = 0 )
218
293
  return text if count > 5 # Stops us getting locked into a cycle if people mess up the insert
@@ -228,21 +303,21 @@ end
228
303
  end
229
304
 
230
305
  def inline_soks_external_link( text )
231
- text.gsub!(/http:\/\/[^ \n<]+/i) { |m| link m }
232
- text.gsub!(/https:\/\/[^ \n<]+/i) { |m| link m }
233
- text.gsub!(/www.[^ \n<]*/i) { |m| link( "http://#{m}", m) }
306
+ text.gsub!(/http:\/\/[^ \n<]+/i) { |m| link m }
307
+ text.gsub!(/https:\/\/[^ \n<]+/i) { |m| link m }
308
+ text.gsub!(/www\.[^ \n<]*/i) { |m| link( "http://#{m}", m) }
234
309
  text.gsub!(/[A-Za-z0-9.]+?@[A-Za-z0-9.]+/) { |m| link( "mailto:#{m}", m) }
235
310
  end
236
311
 
237
312
  def inline_soks_bracketed_link( text )
238
- text.gsub!(/\[\[([^\]]+)\]\]/) do |m|
239
- title, *pagename = m[2..-3].split(':').map { |t| t.strip }
240
- pagename = pagename.empty? ? title : pagename.join(':')
313
+ text.gsub!(/\[\[\s*(.*?)\s*(|=>\s*(.*?)\s*)\]\]/) do |m|
314
+ title, pagename = $1, $3
315
+ pagename ||= title
241
316
  case pagename
242
- when /^www./i ; link("http://#{pagename}", title )
317
+ when /^www\./i ; link("http://#{pagename}", title )
243
318
  when /[A-Za-z0-9.]+?@[A-Za-z0-9.]+/ ; link("mailto:#{pagename}",title)
244
- when /^http/i ; link(pagename,title)
245
- else ; wiki_link( pagename, title )
319
+ when /^http:\/\//i ; link(pagename,title)
320
+ else ; wiki_link( pagename, title )
246
321
  end
247
322
  end
248
323
  end
@@ -256,17 +331,28 @@ end
256
331
  text.replace( linkedtext )
257
332
  end
258
333
 
259
- def wiki_link( pagename, title, css = nil )
334
+ def wiki_link( pagename, title, css_class = nil )
260
335
  if @wiki.exists? pagename
261
336
  @internal_links_from_page << @wiki.page( pagename )
262
- link(pagename,title, css || '' )
263
- else
264
- link(pagename,title, css || 'missing')
337
+ css_class ||= ''
265
338
  end
339
+ link(url_encode_pagename( pagename ),title, css_class || 'missing')
340
+ end
341
+
342
+ def link( url, title = url, css_class = '' )
343
+ shelve "<a href='#{url}' class='#{css_class}'>#{title}</a>"
266
344
  end
267
345
 
268
- def link( url, title = url, css = '' )
269
- shelve "<a href='#{url}' class='#{css}'>#{title}</a>"
346
+ # This makes sure punctuation can be used in the name of the page
347
+ # by url encoding anything that isn't a letter of a number.
348
+ # The complexity is to check that a command hasn't been embedded inside
349
+ # a pagename (e.g. /edit/home page ) in which case the command should not
350
+ # be encoded.
351
+ def url_encode_pagename( pagename )
352
+ if pagename =~ /\/(\w+)\/(.*)/
353
+ return "/#{$1}/#{$2.url_encode}"
354
+ end
355
+ return pagename.url_encode
270
356
  end
271
357
 
272
358
  def hide_textile_image_tags( text )
@@ -276,7 +362,6 @@ end
276
362
  text
277
363
  end
278
364
 
279
-
280
365
  def hide_textile_links( text )
281
366
  text.gsub!( LINK_RE ) do |match|
282
367
  hide_textile match
@@ -303,13 +388,13 @@ end
303
388
  end
304
389
 
305
390
  class View
306
- include Notify
307
391
  attr_reader :rollingmatch, :links
308
392
 
309
393
  def initialize( wiki, name )
310
394
  @wikiname = name
311
395
  @rollingmatch, @links, @redcloth_cache, @erb_cache = RollingMatch.new, Links.new, Hash.new, Hash.new
312
396
  @wiki = wiki
397
+ wiki.watch_attentively_for( :page_revised ) { |event,page,revision| refresh_redcloth( page ) }
313
398
  end
314
399
 
315
400
  def view( pagename, view = 'view', person = nil )
@@ -325,6 +410,7 @@ class View
325
410
 
326
411
  def find( pagename )
327
412
  return view( pagename ) if @wiki.exists?( pagename )
413
+ view = 'find'
328
414
  search_term = /#{pagename}/i
329
415
  title_results = @wiki.select { |name,page| name=~ search_term }
330
416
  text_results = @wiki.select { |name,page| page.content=~ search_term }
@@ -334,42 +420,30 @@ class View
334
420
  end
335
421
 
336
422
  def revise( pagename, content, person, newpagename = pagename )
337
- if @wiki.exists? pagename
338
- unless newpagename == pagename
339
- mutate( pagename ) { @wiki.revise( pagename, "Content moved to [[#{newpagename}]]", person ) }
340
- mutate( newpagename ) { @wiki.revise( newpagename, "Content moved from [[#{pagename}]]", person ) }
341
- end
423
+ if @wiki.exists?( pagename ) && (newpagename != pagename)
424
+ @wiki.revise( pagename, "#{$MESSAGES[:content_moved_to]} [[#{newpagename}]]", person )
425
+ @wiki.revise( newpagename, "#{$MESSAGES[:content_moved_from]} [[#{pagename}]]", 'AutomaticPageMover' )
426
+ @wiki.revise( newpagename, content, person )
427
+ else
428
+ @wiki.revise( newpagename, content, person )
342
429
  end
343
- mutate( newpagename ) { @wiki.revise( newpagename, content, person ) }
430
+
344
431
  end
345
432
 
346
433
  def move( oldpagename, person, newpagename )
347
434
  unless newpagename == oldpagename
348
- mutate( newpagename ) { @wiki.revise( newpagename, "Content moved from [[#{pagename}]]", person ) }
349
- mutate( newpagename ) { @wiki.revise( newpagename, @wiki.page( oldpagename ).content, person ) }
350
- mutate( oldpagename ) { @wiki.revise( oldpagename, "Content moved to [[#{newpagename}]]", person ) }
435
+ @wiki.revise( newpagename, "#{$MESSAGES[:content_moved_from]} [[#{pagename}]]", 'AutomaticPageMover')
436
+ @wiki.revise( newpagename, @wiki.page( oldpagename ).content, person)
437
+ @wiki.revise( oldpagename, "#{$MESSAGES[:content_moved_to]} [[#{newpagename}]]", person)
351
438
  end
352
439
  end
353
440
 
354
441
  def rollback( pagename, revision, person )
355
- mutate( pagename ) { @wiki.rollback( pagename, revision, person ) }
442
+ @wiki.rollback( pagename, revision, person )
356
443
  end
357
444
 
358
445
  def delete( pagename, person )
359
- mutate( pagename ) { @wiki.revise( pagename, 'Page deleted', person ) }
360
- end
361
-
362
- def mutate( pagename )
363
- didexist = @wiki.exists? pagename
364
- yield
365
- page = @wiki.page( pagename )
366
- clear_redcloth_cache( page )
367
- notify :page_revised, page, page.revisions.last
368
- if page.deleted?
369
- notify :page_deleted, page
370
- elsif !didexist
371
- notify :page_created, page
372
- end
446
+ @wiki.revise( pagename, $MESSAGES[:page_deleted], person )
373
447
  end
374
448
 
375
449
  def refresh_redcloth( page )
@@ -403,7 +477,7 @@ class View
403
477
  def path_for( klass, view ) "#{$SETTINGS[:root_directory]}/views/#{klass}_#{view}.rhtml" end
404
478
 
405
479
  def should_frame?( view )
406
- return true unless ['print','rss'].include? view.downcase
480
+ return true unless $SETTINGS[:dont_frame_templates].include? view.downcase
407
481
  end
408
482
 
409
483
  def frame_erb