instiki/app/controllers/wiki_controller.rb
Jacques Distler bceb1864df Fixes
Fix Session CookieOverflow bug when rescuing an InstikiValidation error.
Fix some random things which will cause problems with Ruby 1.9. (Plenty
more where those came from.)
2008-11-05 22:24:14 -06:00

447 lines
14 KiB
Ruby

require 'fileutils'
require 'maruku'
require 'zip/zip'
require 'stringsupport'
require 'resolv'
class WikiController < ApplicationController
before_filter :load_page
caches_action :show, :published, :authors, :tex, :s5, :print, :recently_revised, :list, :atom_with_content, :atom_with_headlines
cache_sweeper :revision_sweeper
layout 'default', :except => [:atom_with_content, :atom_with_headlines, :atom, :tex, :s5, :export_html]
def index
if @web_name
redirect_home
elsif not @wiki.setup?
redirect_to :controller => 'admin', :action => 'create_system'
elsif @wiki.webs.length == 1
redirect_home @wiki.webs.values.first.address
else
redirect_to :action => 'web_list'
end
end
# Outside a single web --------------------------------------------------------
def authenticate
if password_check(params['password'])
redirect_home
else
flash[:info] = password_error(params['password'])
redirect_to :action => 'login', :web => @web_name
end
end
def login
# to template
end
def web_list
@webs = wiki.webs.values.sort_by { |web| web.name }
end
# Within a single web ---------------------------------------------------------
def authors
@page_names_by_author = @web.page_names_by_author
@authors = @page_names_by_author.keys.sort
end
def export_html
stylesheet = File.read(File.join(RAILS_ROOT, 'public', 'stylesheets', 'instiki.css'))
export_pages_as_zip('html') do |page|
renderer = PageRenderer.new(page.revisions.last)
rendered_page = <<-EOL
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>#{page.plain_name} in #{@web.name}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">
h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover {
color: ##{@web ? @web.color : "393" };
}
.newWikiWord { background-color: white; font-style: italic; }
#{stylesheet}
</style>
<style type="text/css">
#{@web.additional_style}
</style>
</head>
<body>
#{renderer.display_content_for_export}
<div class="byline">
#{page.revisions? ? "Revised" : "Created" } on #{ page.revised_at.strftime('%B %d, %Y %H:%M:%S') }
by
#{ UrlGenerator.new(self).make_link(page.author.name, @web, nil, { :mode => :export }) }
</div>
</body>
</html>
EOL
rendered_page
end
end
def export_markup
export_pages_as_zip(@web.markup) { |page| page.content }
end
# def export_pdf
# file_name = "#{@web.address}-tex-#{@web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}"
# file_path = File.join(@wiki.storage_path, file_name)
#
# export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex"
# convert_tex_to_pdf "#{file_path}.tex"
# send_file "#{file_path}.pdf"
# end
# def export_tex
# file_name = "#{@web.address}-tex-#{@web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}.tex"
# file_path = File.join(@wiki.storage_path, file_name)
# export_web_to_tex(file_path) unless FileTest.exists?(file_path)
# send_file file_path
# end
def feeds
@rss_with_content_allowed = rss_with_content_allowed?
# show the template
end
def list
parse_category
@page_names_that_are_wanted = @pages_in_category.wanted_pages
@pages_that_are_orphaned = @pages_in_category.orphaned_pages
end
def recently_revised
parse_category
@pages_by_revision = @pages_in_category.by_revision
@pages_by_day = Hash.new { |h, day| h[day] = [] }
@pages_by_revision.each do |page|
day = Date.new(page.revised_at.year, page.revised_at.month, page.revised_at.day)
@pages_by_day[day] << page
end
end
def atom_with_content
if rss_with_content_allowed?
render_atom(hide_description = false)
else
render :text => 'Atom feed with content for this web is blocked for security reasons. ' +
'The web is password-protected and not published', :status => 403, :layout => 'error'
end
end
def atom_with_headlines
render_atom(hide_description = true)
end
def search
@query = params['query']
render(:text => "Your query string was not valid utf-8", :layout => 'error', :status => 400) and return unless @query.is_utf8?
@title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort
@results = @web.select { |page| page.content =~ /#{@query}/i }.sort
all_pages_found = (@results + @title_results).uniq
if all_pages_found.size == 1
redirect_to_page(all_pages_found.first.name)
end
end
# Within a single page --------------------------------------------------------
def cancel_edit
@page.unlock
redirect_to_page(@page_name)
end
def edit
if @page.nil? or not @page_name.is_utf8?
redirect_home
elsif @page.locked?(Time.now) and not params['break_lock']
redirect_to :web => @web_name, :action => 'locked', :id => @page_name
else
@page.lock(Time.now, @author)
end
end
def locked
render(:text => 'Page name is not valid utf-8.', :status => 400, :layout => 'error') unless @page_name.is_utf8?
# to template
end
def new
render(:text => 'Page name is not valid utf-8.', :status => 400, :layout => 'error') unless @page_name.is_utf8?
# to template
end
# def pdf
# page = wiki.read_page(@web_name, @page_name)
# safe_page_name = @page.name.gsub(/\W/, '')
# file_name = "#{safe_page_name}-#{@web.address}-#{@page.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}"
# file_path = File.join(@wiki.storage_path, file_name)
#
# export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex")
# # NB: this is _very_ slow
# convert_tex_to_pdf("#{file_path}.tex")
# send_file "#{file_path}.pdf"
# end
def print
if @page.nil?
redirect_home
end
@link_mode ||= :show
@renderer = PageRenderer.new(@page.revisions.last)
# to template
end
def published
if not @web.published?
render(:text => "Published version of web '#{@web_name}' is not available", :status => 404, :layout => 'error')
return
end
@page_name ||= 'HomePage'
@page ||= wiki.read_page(@web_name, @page_name)
@link_mode ||= :publish
render(:text => "Page '#{@page_name}' not found", :status => 404, :layout => 'error') and return unless @page
@renderer = PageRenderer.new(@page.revisions.last)
end
def revision
get_page_and_revision
@show_diff = (params[:mode] == 'diff')
@renderer = PageRenderer.new(@revision)
end
def rollback
get_page_and_revision
end
def save
render(:status => 404, :text => 'Undefined page name', :layout => 'error') and return if @page_name.nil? or not @page_name.is_utf8?
unless (request.post? || ENV["RAILS_ENV"] == "test")
headers['Allow'] = 'POST'
render(:status => 405, :text => 'You must use an HTTP POST', :layout => 'error')
return
end
author_name = params['author']
author_name = 'AnonymousCoward' if author_name =~ /^\s*$/
render(:text => "Your name was not valid utf-8", :layout => 'error', :status => 400) and return unless author_name.is_utf8?
cookies['author'] = { :value => author_name, :expires => Time.utc(2030) }
begin
the_content = params['content']
filter_spam(the_content)
raise Instiki::ValidationError.new('Your content was not valid utf-8.') unless the_content.is_utf8?
if @page
wiki.revise_page(@web_name, @page_name, the_content, Time.now,
Author.new(author_name, remote_ip), PageRenderer.new)
@page.unlock
else
wiki.write_page(@web_name, @page_name, the_content, Time.now,
Author.new(author_name, remote_ip), PageRenderer.new)
end
redirect_to_page @page_name
rescue Instiki::ValidationError => e
flash[:error] = e.to_s
logger.error e
if @page
@page.unlock
redirect_to :action => 'edit', :web => @web_name, :id => @page_name
else
redirect_to :action => 'new', :web => @web_name, :id => @page_name
end
end
end
def show
if @page
begin
@renderer = PageRenderer.new(@page.revisions.last)
@show_diff = (params[:mode] == 'diff')
render :action => 'page'
# TODO this rescue should differentiate between errors due to rendering and errors in
# the application itself (for application errors, it's better not to rescue the error at all)
rescue => e
logger.error e
flash[:error] = e.to_s
if in_a_web?
redirect_to :action => 'edit', :web => @web_name, :id => @page_name
else
raise e
end
end
else
if not @page_name.nil? and @page_name.is_utf8? and not @page_name.empty?
redirect_to :web => @web_name, :action => 'new', :id => @page_name
else
render :text => 'Page name is not specified', :status => 404, :layout => 'error'
end
end
end
def tex
if @web.markup == :markdownMML or @web.markup == :markdownPNG or @web.markup == :markdown
@tex_content = Maruku.new(@page.content).to_latex
else
@tex_content = 'TeX export only supported with the Markdown text filters.'
end
end
def s5
if @web.markup == :markdownMML || @web.markup == :markdownPNG || @web.markup == :markdown
my_rendered = PageRenderer.new(@page.revisions.last)
@s5_content = my_rendered.display_s5
@s5_theme = my_rendered.s5_theme
else
@s5_content = "S5 not supported with this text filter"
@s5_theme = "default"
end
end
protected
def load_page
@page_name = params['id']
@page = @wiki.read_page(@web_name, @page_name) if @page_name
end
private
# def convert_tex_to_pdf(tex_path)
# # TODO remove earlier PDF files with the same prefix
# # TODO handle gracefully situation where pdflatex is not available
# begin
# wd = Dir.getwd
# Dir.chdir(File.dirname(tex_path))
# logger.info `pdflatex --interaction=nonstopmode #{File.basename(tex_path)}`
# ensure
# Dir.chdir(wd)
# end
# end
def export_page_to_tex(file_path)
if @web.markup == :markdownMML || @web.markup == :markdownPNG
@tex_content = Maruku.new(@page.content).to_latex
else
@tex_content = 'TeX export only supported with the Markdown text filters.'
end
File.open(file_path, 'w') { |f| f.write(render_to_string(:template => 'wiki/tex', :layout => 'tex')) }
end
def export_pages_as_zip(file_type, &block)
file_prefix = "#{@web.address}-#{file_type}-"
timestamp = @web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')
file_path = File.join(@wiki.storage_path, file_prefix + timestamp + '.zip')
tmp_path = "#{file_path}.tmp"
Zip::ZipOutputStream.open(tmp_path) do |zip_out|
@web.select.by_name.each do |page|
zip_out.put_next_entry("#{CGI.escape(page.name)}.#{file_type}")
zip_out.puts(block.call(page))
end
# add an index file, if exporting to HTML
if file_type.to_s.downcase == 'html'
zip_out.put_next_entry 'index.html'
zip_out.puts "<html><head>" +
"<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;URL=HomePage.#{file_type}\"></head></html>"
end
end
FileUtils.rm_rf(Dir[File.join(@wiki.storage_path, file_prefix + '*.zip')])
FileUtils.mv(tmp_path, file_path)
send_file file_path
end
# def export_web_to_tex(file_path)
# if @web.markup == :markdownMML
# @tex_content = Maruku.new(@page.content).to_latex
# else
# @tex_content = 'TeX export only supported with the Markdown text filters.'
# end
# @tex_content = table_of_contents(@web.page('HomePage').content, render_tex_web)
# File.open(file_path, 'w') { |f| f.write(render_to_string(:template => 'wiki/tex_web', :layout => tex)) }
# end
def get_page_and_revision
if params['rev']
@revision_number = params['rev'].to_i
else
@revision_number = @page.revisions.length
end
@revision = @page.revisions[@revision_number - 1]
end
def parse_category
@categories = WikiReference.list_categories(@web).sort
@category = params['category']
if @category
@set_name = "category '#{@category}'"
pages = WikiReference.pages_in_category(@web, @category).sort.map { |page_name| @web.page(page_name) }
@pages_in_category = PageSet.new(@web, pages)
else
# no category specified, return all pages of the web
@pages_in_category = @web.select_all.by_name
@set_name = 'the web'
end
end
def remote_ip
ip = request.remote_ip
logger.info(ip)
ip.dup.gsub!(Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex), '\0') || 'bogus address'
end
def render_atom(hide_description = false, limit = 15)
@pages_by_revision = @web.select.by_revision.first(limit)
@hide_description = hide_description
@link_action = @web.password ? 'published' : 'show'
render :action => 'atom'
end
def render_tex_web
@web.select.by_name.inject({}) do |tex_web, page|
if @web.markup == :markdownMML || @web.markup == :markdownPNG
tex_web[page.name] = Maruku.new(page.content).to_latex
else
tex_web[page.name] = 'TeX export only supported with the Markdown text filters.'
end
tex_web
end
end
def rss_with_content_allowed?
@web.password.nil? or @web.published?
end
def truncate(text, length = 30, truncate_string = '...')
if text.length > length then text[0..(length - 3)] + truncate_string else text end
end
def filter_spam(content)
@@spam_patterns ||= load_spam_patterns
@@spam_patterns.each do |pattern|
raise "Your edit was blocked by spam filtering" if content =~ pattern
end
end
def load_spam_patterns
spam_patterns_file = "#{RAILS_ROOT}/config/spam_patterns.txt"
if File.exists?(spam_patterns_file)
File.readlines(spam_patterns_file).inject([]) { |patterns, line| patterns << Regexp.new(line.chomp, Regexp::IGNORECASE) }
else
[]
end
end
end