instiki 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. data/CHANGELOG +174 -165
  2. data/README +68 -68
  3. data/app/controllers/admin_controller.rb +94 -94
  4. data/app/controllers/application.rb +135 -131
  5. data/app/controllers/file_controller.rb +129 -129
  6. data/app/controllers/wiki_controller.rb +354 -354
  7. data/app/helpers/application_helper.rb +68 -68
  8. data/app/models/author.rb +3 -3
  9. data/app/models/chunks/category.rb +33 -33
  10. data/app/models/chunks/chunk.rb +86 -86
  11. data/app/models/chunks/engines.rb +61 -54
  12. data/app/models/chunks/include.rb +41 -41
  13. data/app/models/chunks/literal.rb +31 -31
  14. data/app/models/chunks/nowiki.rb +28 -28
  15. data/app/models/chunks/test.rb +18 -18
  16. data/app/models/chunks/uri.rb +182 -182
  17. data/app/models/chunks/wiki.rb +141 -141
  18. data/app/models/file_yard.rb +58 -58
  19. data/app/models/page.rb +112 -112
  20. data/app/models/page_lock.rb +22 -22
  21. data/app/models/page_set.rb +89 -89
  22. data/app/models/revision.rb +123 -123
  23. data/app/models/web.rb +182 -176
  24. data/app/models/wiki_content.rb +207 -207
  25. data/app/models/wiki_service.rb +233 -233
  26. data/app/models/wiki_words.rb +23 -23
  27. data/app/views/admin/create_system.rhtml +83 -83
  28. data/app/views/admin/create_web.rhtml +69 -69
  29. data/app/views/admin/edit_web.rhtml +137 -136
  30. data/app/views/file/file.rhtml +18 -18
  31. data/app/views/file/import.rhtml +22 -22
  32. data/app/views/layouts/default.rhtml +86 -85
  33. data/app/views/markdown_help.rhtml +12 -12
  34. data/app/views/mixed_help.rhtml +6 -6
  35. data/app/views/navigation.rhtml +30 -30
  36. data/app/views/rdoc_help.rhtml +12 -12
  37. data/app/views/textile_help.rhtml +24 -24
  38. data/app/views/wiki/authors.rhtml +11 -11
  39. data/app/views/wiki/edit.rhtml +39 -39
  40. data/app/views/wiki/export.rhtml +12 -12
  41. data/app/views/wiki/feeds.rhtml +14 -14
  42. data/app/views/wiki/list.rhtml +64 -64
  43. data/app/views/wiki/locked.rhtml +23 -23
  44. data/app/views/wiki/login.rhtml +14 -14
  45. data/app/views/wiki/new.rhtml +31 -31
  46. data/app/views/wiki/page.rhtml +115 -115
  47. data/app/views/wiki/print.rhtml +14 -14
  48. data/app/views/wiki/published.rhtml +9 -9
  49. data/app/views/wiki/recently_revised.rhtml +26 -26
  50. data/app/views/wiki/revision.rhtml +103 -103
  51. data/app/views/wiki/rollback.rhtml +36 -36
  52. data/app/views/wiki/rss_feed.rhtml +22 -22
  53. data/app/views/wiki/search.rhtml +38 -38
  54. data/app/views/wiki/tex.rhtml +22 -22
  55. data/app/views/wiki/tex_web.rhtml +34 -34
  56. data/app/views/wiki/web_list.rhtml +18 -18
  57. data/app/views/wiki_words_help.rhtml +9 -9
  58. data/config/environment.rb +82 -82
  59. data/config/environments/development.rb +5 -5
  60. data/config/environments/production.rb +4 -4
  61. data/config/environments/test.rb +17 -17
  62. data/config/routes.rb +18 -18
  63. data/lib/active_record_stub.rb +31 -31
  64. data/lib/bluecloth_tweaked.rb +1127 -0
  65. data/lib/diff.rb +444 -444
  66. data/lib/instiki_errors.rb +14 -14
  67. data/lib/rdocsupport.rb +151 -151
  68. data/lib/redcloth_for_tex.rb +736 -736
  69. data/natives/osx/desktop_launcher/AppDelegate.h +18 -18
  70. data/natives/osx/desktop_launcher/AppDelegate.mm +109 -109
  71. data/natives/osx/desktop_launcher/Credits.html +15 -15
  72. data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib +12 -12
  73. data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib +24 -24
  74. data/natives/osx/desktop_launcher/Info.plist +12 -12
  75. data/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj +592 -592
  76. data/natives/osx/desktop_launcher/Instiki_Prefix.pch +7 -7
  77. data/natives/osx/desktop_launcher/MakeDMG.sh +9 -9
  78. data/natives/osx/desktop_launcher/main.mm +14 -14
  79. data/natives/osx/desktop_launcher/version.plist +16 -16
  80. data/public/404.html +5 -5
  81. data/public/500.html +5 -5
  82. data/public/dispatch.rb +9 -9
  83. data/public/javascripts/edit_web.js +52 -52
  84. data/public/javascripts/prototype.js +336 -336
  85. data/public/stylesheets/instiki.css +222 -222
  86. data/script/breakpointer +4 -4
  87. data/script/server +93 -93
  88. metadata +4 -3
@@ -1,129 +1,129 @@
1
- require 'fileutils'
2
- require 'application'
3
- require 'instiki_errors'
4
-
5
- # Controller that is responsible for serving files and pictures.
6
- # Disabled in version 0.10
7
-
8
- class FileController < ApplicationController
9
-
10
- layout 'default'
11
-
12
- before_filter :check_allow_uploads
13
-
14
- def file
15
- check_path
16
- if @params['file']
17
- # form supplied
18
- file_yard.upload_file(@file_name, @params['file'])
19
- flash[:info] = "File '#{@file_name}' successfully uploaded"
20
- @web.refresh_pages_with_references(@file_name)
21
- return_to_last_remembered
22
- elsif file_yard.has_file?(@file_name)
23
- send_file(file_yard.file_path(@file_name))
24
- else
25
- logger.debug("File not found: #{file_yard.files_path}/#{@file_name}")
26
- # go to the template, which is a file upload form
27
- end
28
- end
29
-
30
- def cancel_upload
31
- return_to_last_remembered
32
- end
33
-
34
- def pic
35
- check_path
36
- if @params['file']
37
- # form supplied
38
- file_yard.upload_file(@file_name, @params['file'])
39
- flash[:info] = "Image '#{@file_name}' successfully uploaded"
40
- @web.refresh_pages_with_references(@file_name)
41
- return_to_last_remembered
42
- elsif file_yard.has_file?(@file_name)
43
- send_file(file_yard.file_path(@file_name))
44
- else
45
- logger.debug("Image not found: #{file_yard.files_path}/#{@file_name}")
46
- render_action 'file'
47
- end
48
- end
49
-
50
- def import
51
- return if file_uploads_disabled?
52
-
53
- check_authorization
54
- if @params['file']
55
- @problems = []
56
- import_file_name = "#{@web.address}-import-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.zip"
57
- file_yard.upload_file(import_file_name, @params['file'])
58
- import_from_archive(file_yard.file_path(import_file_name))
59
- if @problems.empty?
60
- flash[:info] = 'Import successfully finished'
61
- else
62
- flash[:info] = "Import finished, but some pages were not imported:<li>" +
63
- @problems.join('</li><li>') + '</li>'
64
- end
65
- return_to_last_remembered
66
- else
67
- # to template
68
- end
69
- end
70
-
71
- protected
72
-
73
- def check_allow_uploads
74
-
75
- # TODO enable file uploads again after 0.10 release
76
- unless RAILS_ENV == 'test'
77
- render_text 'File uploads are not ready for general use in Instiki 0.10', '403 Forbidden'
78
- return false
79
- end
80
-
81
- unless @web.allow_uploads
82
- render_text 'File uploads are blocked by the webmaster', '403 Forbidden'
83
- return false
84
- end
85
- end
86
-
87
-
88
- private
89
-
90
- def check_path
91
- raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name
92
- raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name
93
- raise Instiki::ValidationError.new("Invalid path: unknown web name") unless @web
94
- end
95
-
96
- def file_yard
97
- @wiki.file_yard(@web)
98
- end
99
-
100
- def import_from_archive(archive)
101
- logger.info "Importing pages from #{archive}"
102
- zip = Zip::ZipInputStream.open(archive)
103
- while (entry = zip.get_next_entry) do
104
- ext_length = File.extname(entry.name).length
105
- page_name = entry.name[0..-(ext_length + 1)]
106
- page_content = entry.get_input_stream.read
107
- logger.info "Processing page '#{page_name}'"
108
- begin
109
- existing_page = @wiki.read_page(@web.address, page_name)
110
- if existing_page
111
- if existing_page.content == page_content
112
- logger.info "Page '#{page_name}' with the same content already exists. Skipping."
113
- next
114
- else
115
- logger.info "Page '#{page_name}' already exists. Adding a new revision to it."
116
- wiki.revise_page(@web.address, page_name, page_content, Time.now, @author)
117
- end
118
- else
119
- wiki.write_page(@web.address, page_name, page_content, Time.now, @author)
120
- end
121
- rescue => e
122
- logger.error(e)
123
- @problems << "#{page_name} : #{e.message}"
124
- end
125
- end
126
- logger.info "Import from #{archive} finished"
127
- end
128
-
129
- end
1
+ require 'fileutils'
2
+ require 'application'
3
+ require 'instiki_errors'
4
+
5
+ # Controller that is responsible for serving files and pictures.
6
+ # Disabled in version 0.10
7
+
8
+ class FileController < ApplicationController
9
+
10
+ layout 'default'
11
+
12
+ before_filter :check_allow_uploads
13
+
14
+ def file
15
+ check_path
16
+ if @params['file']
17
+ # form supplied
18
+ file_yard.upload_file(@file_name, @params['file'])
19
+ flash[:info] = "File '#{@file_name}' successfully uploaded"
20
+ @web.refresh_pages_with_references(@file_name)
21
+ return_to_last_remembered
22
+ elsif file_yard.has_file?(@file_name)
23
+ send_file(file_yard.file_path(@file_name))
24
+ else
25
+ logger.debug("File not found: #{file_yard.files_path}/#{@file_name}")
26
+ # go to the template, which is a file upload form
27
+ end
28
+ end
29
+
30
+ def cancel_upload
31
+ return_to_last_remembered
32
+ end
33
+
34
+ def pic
35
+ check_path
36
+ if @params['file']
37
+ # form supplied
38
+ file_yard.upload_file(@file_name, @params['file'])
39
+ flash[:info] = "Image '#{@file_name}' successfully uploaded"
40
+ @web.refresh_pages_with_references(@file_name)
41
+ return_to_last_remembered
42
+ elsif file_yard.has_file?(@file_name)
43
+ send_file(file_yard.file_path(@file_name))
44
+ else
45
+ logger.debug("Image not found: #{file_yard.files_path}/#{@file_name}")
46
+ render_action 'file'
47
+ end
48
+ end
49
+
50
+ def import
51
+ return if file_uploads_disabled?
52
+
53
+ check_authorization
54
+ if @params['file']
55
+ @problems = []
56
+ import_file_name = "#{@web.address}-import-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.zip"
57
+ file_yard.upload_file(import_file_name, @params['file'])
58
+ import_from_archive(file_yard.file_path(import_file_name))
59
+ if @problems.empty?
60
+ flash[:info] = 'Import successfully finished'
61
+ else
62
+ flash[:info] = "Import finished, but some pages were not imported:<li>" +
63
+ @problems.join('</li><li>') + '</li>'
64
+ end
65
+ return_to_last_remembered
66
+ else
67
+ # to template
68
+ end
69
+ end
70
+
71
+ protected
72
+
73
+ def check_allow_uploads
74
+
75
+ # TODO enable file uploads again after 0.10 release
76
+ unless RAILS_ENV == 'test'
77
+ render_text 'File uploads are not ready for general use in Instiki 0.10', '403 Forbidden'
78
+ return false
79
+ end
80
+
81
+ unless @web.allow_uploads
82
+ render_text 'File uploads are blocked by the webmaster', '403 Forbidden'
83
+ return false
84
+ end
85
+ end
86
+
87
+
88
+ private
89
+
90
+ def check_path
91
+ raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name
92
+ raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name
93
+ raise Instiki::ValidationError.new("Invalid path: unknown web name") unless @web
94
+ end
95
+
96
+ def file_yard
97
+ @wiki.file_yard(@web)
98
+ end
99
+
100
+ def import_from_archive(archive)
101
+ logger.info "Importing pages from #{archive}"
102
+ zip = Zip::ZipInputStream.open(archive)
103
+ while (entry = zip.get_next_entry) do
104
+ ext_length = File.extname(entry.name).length
105
+ page_name = entry.name[0..-(ext_length + 1)]
106
+ page_content = entry.get_input_stream.read
107
+ logger.info "Processing page '#{page_name}'"
108
+ begin
109
+ existing_page = @wiki.read_page(@web.address, page_name)
110
+ if existing_page
111
+ if existing_page.content == page_content
112
+ logger.info "Page '#{page_name}' with the same content already exists. Skipping."
113
+ next
114
+ else
115
+ logger.info "Page '#{page_name}' already exists. Adding a new revision to it."
116
+ wiki.revise_page(@web.address, page_name, page_content, Time.now, @author)
117
+ end
118
+ else
119
+ wiki.write_page(@web.address, page_name, page_content, Time.now, @author)
120
+ end
121
+ rescue => e
122
+ logger.error(e)
123
+ @problems << "#{page_name} : #{e.message}"
124
+ end
125
+ end
126
+ logger.info "Import from #{archive} finished"
127
+ end
128
+
129
+ end
@@ -1,354 +1,354 @@
1
- require 'application'
2
- require 'fileutils'
3
- require 'redcloth_for_tex'
4
- require 'parsedate'
5
-
6
- class WikiController < ApplicationController
7
-
8
- layout 'default', :except => [:rss_feed, :rss_with_content, :rss_with_headlines, :tex, :export_tex, :export_html]
9
-
10
- def index
11
- if @web_name
12
- redirect_show 'HomePage'
13
- elsif not @wiki.setup?
14
- redirect_to :controller => 'admin', :action => 'create_system'
15
- elsif @wiki.webs.length == 1
16
- redirect_show 'HomePage', @wiki.webs.values.first.address
17
- else
18
- redirect_to :action => 'web_list'
19
- end
20
- end
21
-
22
- # Outside a single web --------------------------------------------------------
23
-
24
- def authenticate
25
- if password_check(@params['password'])
26
- redirect_show('HomePage')
27
- else
28
- flash[:info] = password_error(@params['password'])
29
- redirect_to :action => 'login', :web => @web_name
30
- end
31
- end
32
-
33
- def login
34
- # to template
35
- end
36
-
37
- def web_list
38
- @webs = wiki.webs.values.sort_by { |web| web.name }
39
- end
40
-
41
-
42
- # Within a single web ---------------------------------------------------------
43
-
44
- def authors
45
- @authors = @web.select.authors
46
- end
47
-
48
- def export_html
49
- export_pages_as_zip('html') do |page|
50
- @page = page
51
- @link_mode = :export
52
- render_to_string 'wiki/print'
53
- end
54
- end
55
-
56
- def export_markup
57
- export_pages_as_zip(@web.markup) { |page| page.content }
58
- end
59
-
60
- def export_pdf
61
- file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}"
62
- file_path = File.join(@wiki.storage_path, file_name)
63
-
64
- export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex"
65
- convert_tex_to_pdf "#{file_path}.tex"
66
- send_file "#{file_path}.pdf"
67
- end
68
-
69
- def export_tex
70
- file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex"
71
- file_path = File.join(@wiki.storage_path, file_name)
72
- export_web_to_tex(file_path) unless FileTest.exists?(file_path)
73
- send_file file_path
74
- end
75
-
76
- def feeds
77
- @rss_with_content_allowed = rss_with_content_allowed?
78
- # show the template
79
- end
80
-
81
- def list
82
- parse_category
83
- @pages_by_name = @pages_in_category.by_name
84
- @page_names_that_are_wanted = @pages_in_category.wanted_pages
85
- @pages_that_are_orphaned = @pages_in_category.orphaned_pages
86
- end
87
-
88
- def recently_revised
89
- parse_category
90
- @pages_by_revision = @pages_in_category.by_revision
91
- end
92
-
93
- def rss_with_content
94
- if rss_with_content_allowed?
95
- render_rss(hide_description = false, *parse_rss_params)
96
- else
97
- render_text 'RSS feed with content for this web is blocked for security reasons. ' +
98
- 'The web is password-protected and not published', '403 Forbidden'
99
- end
100
- end
101
-
102
- def rss_with_headlines
103
- render_rss(hide_description = true, *parse_rss_params)
104
- end
105
-
106
- def search
107
- @query = @params['query']
108
- @title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort
109
- @results = @web.select { |page| page.content =~ /#{@query}/i }.sort
110
- all_pages_found = (@results + @title_results).uniq
111
- if all_pages_found.size == 1
112
- redirect_show(all_pages_found.first.name)
113
- end
114
- end
115
-
116
- # Within a single page --------------------------------------------------------
117
-
118
- def cancel_edit
119
- @page.unlock
120
- redirect_show
121
- end
122
-
123
- def edit
124
- if @page.nil?
125
- redirect_to :action => 'index'
126
- elsif @page.locked?(Time.now) and not @params['break_lock']
127
- redirect_to :web => @web_name, :action => 'locked', :id => @page_name
128
- else
129
- @page.lock(Time.now, @author)
130
- end
131
- end
132
-
133
- def locked
134
- # to template
135
- end
136
-
137
- def new
138
- # to template
139
- end
140
-
141
- def pdf
142
- page = wiki.read_page(@web_name, @page_name)
143
- safe_page_name = @page.name.gsub(/\W/, '')
144
- file_name = "#{safe_page_name}-#{@web.address}-#{@page.created_at.strftime('%Y-%m-%d-%H-%M-%S')}"
145
- file_path = File.join(@wiki.storage_path, file_name)
146
-
147
- export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex")
148
- # NB: this is _very_ slow
149
- convert_tex_to_pdf("#{file_path}.tex")
150
- send_file "#{file_path}.pdf"
151
- end
152
-
153
- def print
154
- @link_mode ||= :show
155
- # to template
156
- end
157
-
158
- def published
159
- if @web.published
160
- @page = wiki.read_page(@web_name, @page_name || 'HomePage')
161
- else
162
- redirect_show('HomePage')
163
- end
164
- end
165
-
166
- def revision
167
- get_page_and_revision
168
- end
169
-
170
- def rollback
171
- get_page_and_revision
172
- end
173
-
174
- def save
175
- redirect_to :action => 'index' if @page_name.nil?
176
- cookies['author'] = @params['author']
177
-
178
- begin
179
- page = @web.pages[@page_name]
180
- if @web.pages[@page_name]
181
- wiki.revise_page(
182
- @web_name, @page_name, @params['content'], Time.now,
183
- Author.new(@params['author'], remote_ip)
184
- )
185
- page.unlock
186
- else
187
- wiki.write_page(
188
- @web_name, @page_name, @params['content'], Time.now,
189
- Author.new(@params['author'], remote_ip)
190
- )
191
- end
192
- redirect_show(@page_name)
193
- rescue Instiki::ValidationError => e
194
- page.unlock if defined? page
195
- flash[:error] = e
196
- return_to_last_remembered
197
- end
198
- end
199
-
200
- def show
201
- if @page
202
- begin
203
- render_action 'page'
204
- # TODO this rescue should differentiate between errors due to rendering and errors in
205
- # the application itself (for application errors, it's better not to rescue the error at all)
206
- rescue => e
207
- logger.error e
208
- if in_a_web?
209
- redirect_to :web => @web_name, :action => 'edit',
210
- :action_suffix => "#{@page_name}?msg=#{e.message}"
211
- else
212
- raise e
213
- end
214
- end
215
- else
216
- if not @page_name.nil? and not @page_name.empty?
217
- redirect_to :web => @web_name, :action => 'new', :id => @page_name
218
- else
219
- render_text 'Page name is not specified', '404 Not Found'
220
- end
221
- end
222
- end
223
-
224
- def tex
225
- @tex_content = RedClothForTex.new(@page.content).to_tex
226
- end
227
-
228
-
229
- private
230
-
231
- def convert_tex_to_pdf(tex_path)
232
- # TODO remove earlier PDF files with the same prefix
233
- # TODO handle gracefully situation where pdflatex is not available
234
- begin
235
- wd = Dir.getwd
236
- Dir.chdir(File.dirname(tex_path))
237
- logger.info `pdflatex --interaction=nonstopmode #{File.basename(tex_path)}`
238
- ensure
239
- Dir.chdir(wd)
240
- end
241
- end
242
-
243
- def export_page_to_tex(file_path)
244
- tex
245
- File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex')) }
246
- end
247
-
248
- def export_pages_as_zip(file_type, &block)
249
-
250
- file_prefix = "#{@web.address}-#{file_type}-"
251
- timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')
252
- file_path = File.join(@wiki.storage_path, file_prefix + timestamp + '.zip')
253
- tmp_path = "#{file_path}.tmp"
254
-
255
- Zip::ZipOutputStream.open(tmp_path) do |zip_out|
256
- @web.select.by_name.each do |page|
257
- zip_out.put_next_entry("#{CGI.escape(page.name)}.#{file_type}")
258
- zip_out.puts(block.call(page))
259
- end
260
- # add an index file, if exporting to HTML
261
- if file_type.to_s.downcase == 'html'
262
- zip_out.put_next_entry 'index.html'
263
- zip_out.puts <<-EOL
264
- <html>
265
- <head>
266
- <META HTTP-EQUIV="Refresh" CONTENT="0;URL=HomePage.#{file_type}">
267
- </head>
268
- </html>
269
- EOL
270
- end
271
- end
272
- FileUtils.rm_rf(Dir[File.join(@wiki.storage_path, file_prefix + '*.zip')])
273
- FileUtils.mv(tmp_path, file_path)
274
- send_file file_path
275
- end
276
-
277
- def export_web_to_tex(file_path)
278
- @tex_content = table_of_contents(@web.pages['HomePage'].content, render_tex_web)
279
- File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) }
280
- end
281
-
282
- def get_page_and_revision
283
- @revision = @page.revisions[@params['rev'].to_i]
284
- end
285
-
286
- def parse_category
287
- @categories = @web.categories
288
- @category = @params['category']
289
- if @categories.include?(@category)
290
- @pages_in_category = @web.select { |page| page.in_category?(@category) }
291
- @set_name = "category '#{@category}'"
292
- else
293
- @pages_in_category = PageSet.new(@web).by_name
294
- @set_name = 'the web'
295
- end
296
- end
297
-
298
- def parse_rss_params
299
- if @params.include? 'limit'
300
- limit = @params['limit'].to_i rescue nil
301
- limit = nil if limit == 0
302
- else
303
- limit = 15
304
- end
305
- start_date = Time.local(*ParseDate::parsedate(@params['start'])) rescue nil
306
- end_date = Time.local(*ParseDate::parsedate(@params['end'])) rescue nil
307
- [ limit, start_date, end_date ]
308
- end
309
-
310
- def remote_ip
311
- ip = @request.remote_ip
312
- logger.info(ip)
313
- ip
314
- end
315
-
316
- def render_rss(hide_description = false, limit = 15, start_date = nil, end_date = nil)
317
- if limit && !start_date && !end_date
318
- @pages_by_revision = @web.select.by_revision.first(limit)
319
- else
320
- @pages_by_revision = @web.select.by_revision
321
- @pages_by_revision.reject! { |page| page.created_at < start_date } if start_date
322
- @pages_by_revision.reject! { |page| page.created_at > end_date } if end_date
323
- end
324
-
325
- @hide_description = hide_description
326
- @response.headers['Content-Type'] = 'text/xml'
327
- @link_action = @web.password ? 'published' : 'show'
328
-
329
- render 'wiki/rss_feed'
330
- end
331
-
332
- def render_tex_web
333
- @web.select.by_name.inject({}) do |tex_web, page|
334
- tex_web[page.name] = RedClothForTex.new(page.content).to_tex
335
- tex_web
336
- end
337
- end
338
-
339
- def render_to_string(template_name)
340
- add_variables_to_assigns
341
- render template_name
342
- @performed_render = false
343
- @template.render_file(template_name)
344
- end
345
-
346
- def rss_with_content_allowed?
347
- @web.password.nil? or @web.published
348
- end
349
-
350
- def truncate(text, length = 30, truncate_string = '...')
351
- if text.length > length then text[0..(length - 3)] + truncate_string else text end
352
- end
353
-
354
- end
1
+ require 'application'
2
+ require 'fileutils'
3
+ require 'redcloth_for_tex'
4
+ require 'parsedate'
5
+
6
+ class WikiController < ApplicationController
7
+
8
+ layout 'default', :except => [:rss_feed, :rss_with_content, :rss_with_headlines, :tex, :export_tex, :export_html]
9
+
10
+ def index
11
+ if @web_name
12
+ redirect_home
13
+ elsif not @wiki.setup?
14
+ redirect_to :controller => 'admin', :action => 'create_system'
15
+ elsif @wiki.webs.length == 1
16
+ redirect_home @wiki.webs.values.first.address
17
+ else
18
+ redirect_to :action => 'web_list'
19
+ end
20
+ end
21
+
22
+ # Outside a single web --------------------------------------------------------
23
+
24
+ def authenticate
25
+ if password_check(@params['password'])
26
+ redirect_home
27
+ else
28
+ flash[:info] = password_error(@params['password'])
29
+ redirect_to :action => 'login', :web => @web_name
30
+ end
31
+ end
32
+
33
+ def login
34
+ # to template
35
+ end
36
+
37
+ def web_list
38
+ @webs = wiki.webs.values.sort_by { |web| web.name }
39
+ end
40
+
41
+
42
+ # Within a single web ---------------------------------------------------------
43
+
44
+ def authors
45
+ @authors = @web.select.authors
46
+ end
47
+
48
+ def export_html
49
+ export_pages_as_zip('html') do |page|
50
+ @page = page
51
+ @link_mode = :export
52
+ render_to_string('wiki/print', use_layout = true)
53
+ end
54
+ end
55
+
56
+ def export_markup
57
+ export_pages_as_zip(@web.markup) { |page| page.content }
58
+ end
59
+
60
+ def export_pdf
61
+ file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}"
62
+ file_path = File.join(@wiki.storage_path, file_name)
63
+
64
+ export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex"
65
+ convert_tex_to_pdf "#{file_path}.tex"
66
+ send_file "#{file_path}.pdf"
67
+ end
68
+
69
+ def export_tex
70
+ file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex"
71
+ file_path = File.join(@wiki.storage_path, file_name)
72
+ export_web_to_tex(file_path) unless FileTest.exists?(file_path)
73
+ send_file file_path
74
+ end
75
+
76
+ def feeds
77
+ @rss_with_content_allowed = rss_with_content_allowed?
78
+ # show the template
79
+ end
80
+
81
+ def list
82
+ parse_category
83
+ @pages_by_name = @pages_in_category.by_name
84
+ @page_names_that_are_wanted = @pages_in_category.wanted_pages
85
+ @pages_that_are_orphaned = @pages_in_category.orphaned_pages
86
+ end
87
+
88
+ def recently_revised
89
+ parse_category
90
+ @pages_by_revision = @pages_in_category.by_revision
91
+ end
92
+
93
+ def rss_with_content
94
+ if rss_with_content_allowed?
95
+ render_rss(hide_description = false, *parse_rss_params)
96
+ else
97
+ render_text 'RSS feed with content for this web is blocked for security reasons. ' +
98
+ 'The web is password-protected and not published', '403 Forbidden'
99
+ end
100
+ end
101
+
102
+ def rss_with_headlines
103
+ render_rss(hide_description = true, *parse_rss_params)
104
+ end
105
+
106
+ def search
107
+ @query = @params['query']
108
+ @title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort
109
+ @results = @web.select { |page| page.content =~ /#{@query}/i }.sort
110
+ all_pages_found = (@results + @title_results).uniq
111
+ if all_pages_found.size == 1
112
+ redirect_to_page(all_pages_found.first.name)
113
+ end
114
+ end
115
+
116
+ # Within a single page --------------------------------------------------------
117
+
118
+ def cancel_edit
119
+ @page.unlock
120
+ redirect_to_page(@page_name)
121
+ end
122
+
123
+ def edit
124
+ if @page.nil?
125
+ redirect_to :action => 'index'
126
+ elsif @page.locked?(Time.now) and not @params['break_lock']
127
+ redirect_to :web => @web_name, :action => 'locked', :id => @page_name
128
+ else
129
+ @page.lock(Time.now, @author)
130
+ end
131
+ end
132
+
133
+ def locked
134
+ # to template
135
+ end
136
+
137
+ def new
138
+ # to template
139
+ end
140
+
141
+ def pdf
142
+ page = wiki.read_page(@web_name, @page_name)
143
+ safe_page_name = @page.name.gsub(/\W/, '')
144
+ file_name = "#{safe_page_name}-#{@web.address}-#{@page.created_at.strftime('%Y-%m-%d-%H-%M-%S')}"
145
+ file_path = File.join(@wiki.storage_path, file_name)
146
+
147
+ export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex")
148
+ # NB: this is _very_ slow
149
+ convert_tex_to_pdf("#{file_path}.tex")
150
+ send_file "#{file_path}.pdf"
151
+ end
152
+
153
+ def print
154
+ @link_mode ||= :show
155
+ # to template
156
+ end
157
+
158
+ def published
159
+ if @web.published
160
+ @page = wiki.read_page(@web_name, @page_name || 'HomePage')
161
+ else
162
+ redirect_home
163
+ end
164
+ end
165
+
166
+ def revision
167
+ get_page_and_revision
168
+ end
169
+
170
+ def rollback
171
+ get_page_and_revision
172
+ end
173
+
174
+ def save
175
+ redirect_to :action => 'index' if @page_name.nil?
176
+ cookies['author'] = @params['author']
177
+
178
+ begin
179
+ page = @web.pages[@page_name]
180
+ if @web.pages[@page_name]
181
+ wiki.revise_page(
182
+ @web_name, @page_name, @params['content'], Time.now,
183
+ Author.new(@params['author'], remote_ip)
184
+ )
185
+ page.unlock
186
+ else
187
+ wiki.write_page(
188
+ @web_name, @page_name, @params['content'], Time.now,
189
+ Author.new(@params['author'], remote_ip)
190
+ )
191
+ end
192
+ redirect_to_page @page_name
193
+ rescue Instiki::ValidationError => e
194
+ page.unlock if defined? page
195
+ flash[:error] = e
196
+ return_to_last_remembered
197
+ end
198
+ end
199
+
200
+ def show
201
+ if @page
202
+ begin
203
+ render_action 'page'
204
+ # TODO this rescue should differentiate between errors due to rendering and errors in
205
+ # the application itself (for application errors, it's better not to rescue the error at all)
206
+ rescue => e
207
+ logger.error e
208
+ if in_a_web?
209
+ redirect_to :web => @web_name, :action => 'edit',
210
+ :action_suffix => "#{@page_name}?msg=#{e.message}"
211
+ else
212
+ raise e
213
+ end
214
+ end
215
+ else
216
+ if not @page_name.nil? and not @page_name.empty?
217
+ redirect_to :web => @web_name, :action => 'new', :id => @page_name
218
+ else
219
+ render_text 'Page name is not specified', '404 Not Found'
220
+ end
221
+ end
222
+ end
223
+
224
+ def tex
225
+ @tex_content = RedClothForTex.new(@page.content).to_tex
226
+ end
227
+
228
+
229
+ private
230
+
231
+ def convert_tex_to_pdf(tex_path)
232
+ # TODO remove earlier PDF files with the same prefix
233
+ # TODO handle gracefully situation where pdflatex is not available
234
+ begin
235
+ wd = Dir.getwd
236
+ Dir.chdir(File.dirname(tex_path))
237
+ logger.info `pdflatex --interaction=nonstopmode #{File.basename(tex_path)}`
238
+ ensure
239
+ Dir.chdir(wd)
240
+ end
241
+ end
242
+
243
+ def export_page_to_tex(file_path)
244
+ tex
245
+ File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex')) }
246
+ end
247
+
248
+ def export_pages_as_zip(file_type, &block)
249
+
250
+ file_prefix = "#{@web.address}-#{file_type}-"
251
+ timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')
252
+ file_path = File.join(@wiki.storage_path, file_prefix + timestamp + '.zip')
253
+ tmp_path = "#{file_path}.tmp"
254
+
255
+ Zip::ZipOutputStream.open(tmp_path) do |zip_out|
256
+ @web.select.by_name.each do |page|
257
+ zip_out.put_next_entry("#{CGI.escape(page.name)}.#{file_type}")
258
+ zip_out.puts(block.call(page))
259
+ end
260
+ # add an index file, if exporting to HTML
261
+ if file_type.to_s.downcase == 'html'
262
+ zip_out.put_next_entry 'index.html'
263
+ zip_out.puts <<-EOL
264
+ <html>
265
+ <head>
266
+ <META HTTP-EQUIV="Refresh" CONTENT="0;URL=HomePage.#{file_type}">
267
+ </head>
268
+ </html>
269
+ EOL
270
+ end
271
+ end
272
+ FileUtils.rm_rf(Dir[File.join(@wiki.storage_path, file_prefix + '*.zip')])
273
+ FileUtils.mv(tmp_path, file_path)
274
+ send_file file_path
275
+ end
276
+
277
+ def export_web_to_tex(file_path)
278
+ @tex_content = table_of_contents(@web.pages['HomePage'].content, render_tex_web)
279
+ File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) }
280
+ end
281
+
282
+ def get_page_and_revision
283
+ @revision = @page.revisions[@params['rev'].to_i]
284
+ end
285
+
286
+ def parse_category
287
+ @categories = @web.categories
288
+ @category = @params['category']
289
+ if @categories.include?(@category)
290
+ @pages_in_category = @web.select { |page| page.in_category?(@category) }
291
+ @set_name = "category '#{@category}'"
292
+ else
293
+ @pages_in_category = PageSet.new(@web).by_name
294
+ @set_name = 'the web'
295
+ end
296
+ end
297
+
298
+ def parse_rss_params
299
+ if @params.include? 'limit'
300
+ limit = @params['limit'].to_i rescue nil
301
+ limit = nil if limit == 0
302
+ else
303
+ limit = 15
304
+ end
305
+ start_date = Time.local(*ParseDate::parsedate(@params['start'])) rescue nil
306
+ end_date = Time.local(*ParseDate::parsedate(@params['end'])) rescue nil
307
+ [ limit, start_date, end_date ]
308
+ end
309
+
310
+ def remote_ip
311
+ ip = @request.remote_ip
312
+ logger.info(ip)
313
+ ip
314
+ end
315
+
316
+ def render_rss(hide_description = false, limit = 15, start_date = nil, end_date = nil)
317
+ if limit && !start_date && !end_date
318
+ @pages_by_revision = @web.select.by_revision.first(limit)
319
+ else
320
+ @pages_by_revision = @web.select.by_revision
321
+ @pages_by_revision.reject! { |page| page.created_at < start_date } if start_date
322
+ @pages_by_revision.reject! { |page| page.created_at > end_date } if end_date
323
+ end
324
+
325
+ @hide_description = hide_description
326
+ @response.headers['Content-Type'] = 'text/xml'
327
+ @link_action = @web.password ? 'published' : 'show'
328
+
329
+ render 'wiki/rss_feed'
330
+ end
331
+
332
+ def render_tex_web
333
+ @web.select.by_name.inject({}) do |tex_web, page|
334
+ tex_web[page.name] = RedClothForTex.new(page.content).to_tex
335
+ tex_web
336
+ end
337
+ end
338
+
339
+ def render_to_string(template_name, with_layout = false)
340
+ add_variables_to_assigns
341
+ @content_for_layout = @template.render_file(template_name)
342
+ if with_layout then @template.render_file('layouts/default');
343
+ else @content_for_layout; end
344
+ end
345
+
346
+ def rss_with_content_allowed?
347
+ @web.password.nil? or @web.published
348
+ end
349
+
350
+ def truncate(text, length = 30, truncate_string = '...')
351
+ if text.length > length then text[0..(length - 3)] + truncate_string else text end
352
+ end
353
+
354
+ end