Checkout of Instiki Trunk 1/21/2007.

This commit is contained in:
Jacques Distler 2007-01-22 07:43:50 -06:00
commit 69b62b6f33
1138 changed files with 139586 additions and 0 deletions

View file

@ -0,0 +1,94 @@
require 'application'
class AdminController < ApplicationController
layout 'default'
cache_sweeper :web_sweeper
def create_system
if @wiki.setup?
flash[:error] =
"Wiki has already been created in '#{@wiki.storage_path}'. " +
"Shut down Instiki and delete this directory if you want to recreate it from scratch." +
"\n\n" +
"(WARNING: this will destroy content of your current wiki)."
redirect_home(@wiki.webs.keys.first)
elsif @params['web_name']
# form submitted -> create a wiki
@wiki.setup(@params['password'], @params['web_name'], @params['web_address'])
flash[:info] = "Your new wiki '#{@params['web_name']}' is created!\n" +
"Please edit its home page and press Submit when finished."
redirect_to :web => @params['web_address'], :controller => 'wiki', :action => 'new',
:id => 'HomePage'
else
# no form submitted -> go to template
end
end
def create_web
if @params['address']
# form submitted
if @wiki.authenticate(@params['system_password'])
begin
@wiki.create_web(@params['name'], @params['address'])
flash[:info] = "New web '#{@params['name']}' successfully created."
redirect_to :web => @params['address'], :controller => 'wiki', :action => 'new',
:id => 'HomePage'
rescue Instiki::ValidationError => e
@error = e.message
# and re-render the form again
end
else
redirect_to :controller => 'wiki', :action => 'index'
end
else
# no form submitted -> render template
end
end
def edit_web
system_password = @params['system_password']
if system_password
# form submitted
if wiki.authenticate(system_password)
begin
wiki.edit_web(
@web.address, @params['address'], @params['name'],
@params['markup'].intern,
@params['color'], @params['additional_style'],
@params['safe_mode'] ? true : false,
@params['password'].empty? ? nil : @params['password'],
@params['published'] ? true : false,
@params['brackets_only'] ? true : false,
@params['count_pages'] ? true : false,
@params['allow_uploads'] ? true : false,
@params['max_upload_size']
)
flash[:info] = "Web '#{@params['address']}' was successfully updated"
redirect_home(@params['address'])
rescue Instiki::ValidationError => e
logger.warn e.message
@error = e.message
# and re-render the same template again
end
else
@error = password_error(system_password)
# and re-render the same template again
end
else
# no form submitted - go to template
end
end
def remove_orphaned_pages
if wiki.authenticate(@params['system_password_orphaned'])
wiki.remove_orphaned_pages(@web_name)
flash[:info] = 'Orphaned pages removed'
redirect_to :controller => 'wiki', :web => @web_name, :action => 'list'
else
flash[:error] = password_error(@params['system_password_orphaned'])
redirect_to :controller => 'admin', :web => @web_name, :action => 'edit_web'
end
end
end

View file

@ -0,0 +1,190 @@
# The filters added to this controller will be run for all controllers in the application.
# Likewise will all the methods added be available for all controllers.
class ApplicationController < ActionController::Base
# require 'dnsbl_check'
before_filter :dnsbl_check, :connect_to_model, :check_authorization, :setup_url_generator, :set_content_type_header, :set_robots_metatag
after_filter :remember_location, :teardown_url_generator
# For injecting a different wiki model implementation. Intended for use in tests
def self.wiki=(the_wiki)
# a global variable is used here because Rails reloads controller and model classes in the
# development environment; therefore, storing it as a class variable does not work
# class variable is, anyway, not much different from a global variable
#$instiki_wiki_service = the_wiki
logger.debug("Wiki service: #{the_wiki.to_s}")
end
def self.wiki
Wiki.new
end
protected
def check_authorization
if in_a_web? and authorization_needed? and not authorized?
redirect_to :controller => 'wiki', :action => 'login', :web => @web_name
return false
end
end
def connect_to_model
@action_name = @params['action'] || 'index'
@web_name = @params['web']
@wiki = wiki
@author = cookies['author'] || 'AnonymousCoward'
if @web_name
@web = @wiki.webs[@web_name]
if @web.nil?
render(:status => 404, :text => "Unknown web '#{@web_name}'")
return false
end
end
end
FILE_TYPES = {
'.exe' => 'application/octet-stream',
'.gif' => 'image/gif',
'.jpg' => 'image/jpeg',
'.pdf' => 'application/pdf',
'.png' => 'image/png',
'.txt' => 'text/plain',
'.zip' => 'application/zip'
} unless defined? FILE_TYPES
DISPOSITION = {
'application/octet-stream' => 'attachment',
'image/gif' => 'inline',
'image/jpeg' => 'inline',
'application/pdf' => 'inline',
'image/png' => 'inline',
'text/plain' => 'inline',
'application/zip' => 'attachment'
} unless defined? DISPOSITION
def determine_file_options_for(file_name, original_options = {})
original_options[:type] ||= (FILE_TYPES[File.extname(file_name)] or 'application/octet-stream')
original_options[:disposition] ||= (DISPOSITION[original_options[:type]] or 'attachment')
original_options[:stream] ||= false
original_options
end
def send_file(file, options = {})
determine_file_options_for(file, options)
super(file, options)
end
def password_check(password)
if password == @web.password
cookies['web_address'] = password
true
else
false
end
end
def password_error(password)
if password.nil? or password.empty?
'Please enter the password.'
else
'You entered a wrong password. Please enter the right one.'
end
end
def redirect_home(web = @web_name)
if web
redirect_to_page('HomePage', web)
else
redirect_to_url '/'
end
end
def redirect_to_page(page_name = @page_name, web = @web_name)
redirect_to :web => web, :controller => 'wiki', :action => 'show',
:id => (page_name or 'HomePage')
end
def remember_location
if @request.method == :get and
@response.headers['Status'] == '200 OK' and not
%w(locked save back file pic import).include?(action_name)
@session[:return_to] = @request.request_uri
logger.debug "Session ##{session.object_id}: remembered URL '#{@session[:return_to]}'"
end
end
def rescue_action_in_public(exception)
render :status => 500, :text => <<-EOL
<html><body>
<h2>Internal Error</h2>
<p>An application error occurred while processing your request.</p>
<!-- \n#{exception}\n#{exception.backtrace.join("\n")}\n -->
</body></html>
EOL
end
def return_to_last_remembered
# Forget the redirect location
redirect_target, @session[:return_to] = @session[:return_to], nil
tried_home, @session[:tried_home] = @session[:tried_home], false
# then try to redirect to it
if redirect_target.nil?
if tried_home
raise 'Application could not render the index page'
else
logger.debug("Session ##{session.object_id}: no remembered redirect location, trying home")
redirect_home
end
else
logger.debug("Session ##{session.object_id}: " +
"redirect to the last remembered URL #{redirect_target}")
redirect_to_url(redirect_target)
end
end
def set_content_type_header
if %w(rss_with_content rss_with_headlines).include?(action_name)
@response.headers['Content-Type'] = 'text/xml; charset=UTF-8'
else
@response.headers['Content-Type'] = 'text/html; charset=UTF-8'
end
end
def set_robots_metatag
if controller_name == 'wiki' and %w(show published).include? action_name
@robots_metatag_value = 'index,follow'
else
@robots_metatag_value = 'noindex,nofollow'
end
end
def setup_url_generator
PageRenderer.setup_url_generator(UrlGenerator.new(self))
end
def teardown_url_generator
PageRenderer.teardown_url_generator
end
def wiki
self.class.wiki
end
private
def in_a_web?
not @web_name.nil?
end
def authorization_needed?
not %w( login authenticate published rss_with_content rss_with_headlines ).include?(action_name)
end
def authorized?
@web.nil? or
@web.password.nil? or
cookies['web_address'] == @web.password or
password_check(@params['password'])
end
end

View file

@ -0,0 +1,23 @@
module CacheSweepingHelper
def expire_cached_page(web, page_name)
expire_action :controller => 'wiki', :web => web.address,
:action => %w(show published), :id => page_name
expire_action :controller => 'wiki', :web => web.address,
:action => %w(show published), :id => page_name, :mode => 'diff'
end
def expire_cached_summary_pages(web)
categories = WikiReference.find(:all, :conditions => "link_type = 'C'")
%w(recently_revised list).each do |action|
expire_action :controller => 'wiki', :web => web.address, :action => action
categories.each do |category|
expire_action :controller => 'wiki', :web => web.address, :action => action, :category => category.referenced_name
end
end
expire_action :controller => 'wiki', :web => web.address, :action => 'authors'
expire_fragment :controller => 'wiki', :web => web.address, :action => %w(rss_with_headlines rss_with_content)
end
end

View file

@ -0,0 +1,100 @@
# Controller responsible for serving files and pictures.
require 'zip/zip'
class FileController < ApplicationController
layout 'default'
before_filter :check_allow_uploads
def file
@file_name = params['id']
if @params['file']
# form supplied
new_file = @web.wiki_files.create(@params['file'])
if new_file.valid?
flash[:info] = "File '#{@file_name}' successfully uploaded"
return_to_last_remembered
else
# pass the file with errors back into the form
@file = new_file
render
end
else
# no form supplied, this is a request to download the file
file = WikiFile.find_by_file_name(@file_name)
if file
send_data(file.content, determine_file_options_for(@file_name, :filename => @file_name))
else
@file = WikiFile.new(:file_name => @file_name)
render
end
end
end
def cancel_upload
return_to_last_remembered
end
def import
if @params['file']
@problems = []
import_file_name = "#{@web.address}-import-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.zip"
import_from_archive(@params['file'].path)
if @problems.empty?
flash[:info] = 'Import successfully finished'
else
flash[:error] = 'Import finished, but some pages were not imported:<li>' +
@problems.join('</li><li>') + '</li>'
end
return_to_last_remembered
else
# to template
end
end
protected
def check_allow_uploads
render(:status => 404, :text => "Web #{@params['web'].inspect} not found") and return false unless @web
if @web.allow_uploads?
return true
else
render :status => 403, :text => 'File uploads are blocked by the webmaster'
return false
end
end
private
def import_from_archive(archive)
logger.info "Importing pages from #{archive}"
zip = Zip::ZipInputStream.open(archive)
while (entry = zip.get_next_entry) do
ext_length = File.extname(entry.name).length
page_name = entry.name[0..-(ext_length + 1)]
page_content = entry.get_input_stream.read
logger.info "Processing page '#{page_name}'"
begin
existing_page = @wiki.read_page(@web.address, page_name)
if existing_page
if existing_page.content == page_content
logger.info "Page '#{page_name}' with the same content already exists. Skipping."
next
else
logger.info "Page '#{page_name}' already exists. Adding a new revision to it."
wiki.revise_page(@web.address, page_name, page_content, Time.now, @author, PageRenderer.new)
end
else
wiki.write_page(@web.address, page_name, page_content, Time.now, @author, PageRenderer.new)
end
rescue => e
logger.error(e)
@problems << "#{page_name} : #{e.message}"
end
end
logger.info "Import from #{archive} finished"
end
end

View file

@ -0,0 +1,29 @@
require_dependency 'cache_sweeping_helper'
class RevisionSweeper < ActionController::Caching::Sweeper
include CacheSweepingHelper
observe Revision, Page
def after_save(record)
if record.is_a?(Revision)
expire_caches(record.page)
end
end
def after_delete(record)
if record.is_a?(Page)
expire_caches(record)
end
end
private
def expire_caches(page)
expire_cached_summary_pages(page.web)
pages_to_expire = ([page.name] + WikiReference.pages_that_reference(page.name)).uniq
pages_to_expire.each { |page_name| expire_cached_page(page.web, page_name) }
end
end

View file

@ -0,0 +1,14 @@
require_dependency 'cache_sweeping_helper'
class WebSweeper < ActionController::Caching::Sweeper
include CacheSweepingHelper
observe Web
def after_save(record)
web = record
web.pages.each { |page| expire_cached_page(web, page.name) }
expire_cached_summary_pages(web)
end
end

View file

@ -0,0 +1,429 @@
require 'fileutils'
require 'redcloth_for_tex'
require 'parsedate'
require 'zip/zip'
class WikiController < ApplicationController
before_filter :load_page
caches_action :show, :published, :authors, :recently_revised, :list
cache_sweeper :revision_sweeper
layout 'default', :except => [:rss_feed, :rss_with_content, :rss_with_headlines, :tex, :export_tex, :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.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 rss_with_content
if rss_with_content_allowed?
render_rss(hide_description = false, *parse_rss_params)
else
render_text 'RSS feed with content for this web is blocked for security reasons. ' +
'The web is password-protected and not published', '403 Forbidden'
end
end
def rss_with_headlines
render_rss(hide_description = true, *parse_rss_params)
end
def search
@query = @params['query']
@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?
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
# to template
end
def new
# 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)
return
end
@page_name ||= 'HomePage'
@page ||= wiki.read_page(@web_name, @page_name)
render(:text => "Page '#{@page_name}' not found", :status => 404) 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') and return if @page_name.nil?
author_name = @params['author']
author_name = 'AnonymousCoward' if author_name =~ /^\s*$/
cookies['author'] = { :value => author_name, :expires => Time.utc(2030) }
begin
filter_spam(@params['content'])
if @page
wiki.revise_page(@web_name, @page_name, @params['content'], Time.now,
Author.new(author_name, remote_ip), PageRenderer.new)
@page.unlock
else
wiki.write_page(@web_name, @page_name, @params['content'], Time.now,
Author.new(author_name, remote_ip), PageRenderer.new)
end
redirect_to_page @page_name
rescue => e
flash[:error] = e
logger.error e
flash[:content] = @params['content']
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.message
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 not @page_name.empty?
redirect_to :web => @web_name, :action => 'new', :id => @page_name
else
render_text 'Page name is not specified', '404 Not Found'
end
end
end
def tex
@tex_content = RedClothForTex.new(@page.content).to_tex
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)
tex
File.open(file_path, 'w') { |f| f.write(render_to_string(:template => 'wiki/tex', :layout => false)) }
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)
@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 => nil)) }
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.sort
@category = @params['category']
if @category
@set_name = "category '#{@category}'"
pages = WikiReference.pages_in_category(@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 parse_rss_params
if @params.include? 'limit'
limit = @params['limit'].to_i rescue nil
limit = nil if limit == 0
else
limit = 15
end
start_date = Time.local(*ParseDate::parsedate(@params['start'])) rescue nil
end_date = Time.local(*ParseDate::parsedate(@params['end'])) rescue nil
[ limit, start_date, end_date ]
end
def remote_ip
ip = @request.remote_ip
logger.info(ip)
ip
end
def render_rss(hide_description = false, limit = 15, start_date = nil, end_date = nil)
if limit && !start_date && !end_date
@pages_by_revision = @web.select.by_revision.first(limit)
else
@pages_by_revision = @web.select.by_revision
@pages_by_revision.reject! { |page| page.revised_at < start_date } if start_date
@pages_by_revision.reject! { |page| page.revised_at > end_date } if end_date
end
@hide_description = hide_description
@link_action = @web.password ? 'published' : 'show'
render :action => 'rss_feed'
end
def render_tex_web
@web.select.by_name.inject({}) do |tex_web, page|
tex_web[page.name] = RedClothForTex.new(page.content).to_tex
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