require "cgi"
require "redcloth_for_tex"
RenderedTodo = Struct.new( :text, :context, :due_date )
class WikiController < ActionControllerServlet
EXPORT_DIRECTORY = WikiService.storage_path unless const_defined?("EXPORT_DIRECTORY")
def index
if web_address
redirect_show "HomePage"
elsif !wiki.setup?
redirect_path "/new_system/"
elsif wiki.webs.length == 1
redirect_show "HomePage", wiki.webs.values.first.address
else
redirect_path "/web_list/"
end
end
# Administrating the Instiki setup --------------------------------------------
def new_system
wiki.setup? ? redirect_path("/") : render
end
def new_web
redirect_path("/") if wiki.system["password"].nil?
end
def create_system
wiki.setup(@params["password"], @params["web_name"], @params["web_address"]) unless wiki.setup?
redirect_path "/"
end
def create_web
redirect_path("/") unless wiki.authenticate(@params["system_password"])
wiki.create_web(@params["name"], @params["address"])
redirect_show("HomePage", @params["address"])
end
# Outside a single web --------------------------------------------------------
def web_list
@system, @webs = wiki.system, wiki.webs.values
render "wiki/web_list"
end
def login
render "wiki/login"
end
def authenticate
password_check(@params["password"]) ? redirect_show("HomePage") : redirect_action("login")
end
def static_style_sheet() render "static_style_sheet" end
# Within a single web ---------------------------------------------------------
def parse_category
@categories = web.categories
@category = @params["category"]
@pages_in_category = web.select { |page| page.in_category?(@category) }
@pages_without_category = web.select { |page| page.categories.length == 0 }
if @category == 'none'
@pages_in_category = @pages_without_category
end
@set_name = ( @categories.include?(@category) ? "category '#{@category}'" : "the web" )
@category_links = @categories.map do |c|
(@category == c ? "[#{c}]" : "#{c}")
end
end
def search
set_menu_pages
@query = @params["query"]
search_content = [nil, 'both', 'contents'].include? @params['fields']
search_names = [nil, 'both', 'names'].include? @params['fields']
case @params['expression']
when 'regex', nil
rex = Regexp.new @query, (Regexp::IGNORECASE unless @params['case'])
@results = if [nil, 'all', 'pages', nil].include? @params['where']
web.select do |page|
(search_names and rex.match(page.name)) or (search_content and rex.match(page.content))
end
else
[]
end
@bliki_results = if [nil, 'all', 'bliki'].include? @params['where']
web.bliki.values.select do |entry|
(search_names and rex.match(entry.name)) or (search_content and rex.match(entry.content))
end
else
[]
end
when 'all'
words = @query.split(/\s/).reject { |w| w.empty? }
@results = if [nil, 'all', 'pages'].include? @params['where']
web.select do |page|
words.all? do |word|
(search_names and page.name.index(word)) or (search_content and page.content.index(word))
end
end
else
[]
end
@bliki_results = if [nil, 'all', 'bliki'].include? @params['where']
web.bliki.values.select do |entry|
words.all? do |word|
(search_names and entry.name.index(word)) or (search_content and entry.content.index(word))
end
end
else
[]
end
when 'exact'
@results = if [nil, 'all', 'pages'].include? @params['where']
web.select do |page|
(search_names and page.name.index(@query)) or (search_content and page.content.index(@query))
end
else
[]
end
@bliki_results = if [nil, 'all', 'bliki'].include? @params['where']
web.bliki.values.select do |entry|
(search_names and entry.name.index(@query)) or (search_content and entry.content.index(@query))
end
else
[]
end
end
if !@params['category'].nil? and @params['category'] != 'noselect'
@selected_categories = parse_multi_select 'category'
@results.reject! { |page| (page.categories & @selected_categories).empty? }
@bliki_results.reject! { |page| (page.categories & @selected_categories).empty? }
end
if !@params['author'].nil? and @params['author'] != 'noselect'
@selected_authors = parse_multi_select 'author'
@results.reject! { |page| (page.authors & @selected_authors).empty? }
@bliki_results.reject! { |page| (page.authors & @selected_authors).empty? }
end
redirect_show(@results.first.name) if @results.length == 1 && @bliki_results.length == 0
redirect_path("/#{web_address}/bliki_revision/#{@bliki_results.first.name}?rev=#{@bliki_results.first.revisions.size-1}") if @results.length == 0 && @bliki_results.length == 1
render_action "search"
end
def authors
@authors = web.select.authors
end
def recently_revised
parse_category
set_menu_pages
@pages_by_revision = @pages_in_category.by_revision
end
def rss_with_content
@pages_by_revision = web.select.by_revision.first(15)
@uri = @req.request_uri
host = @req.meta_vars["HTTP_X_FORWARDED_HOST"] || "#{@uri.host}:#{@uri.port.to_s}"
@web_url = "#{@uri.scheme}://#{host}/#{@web.address}"
@res["Content-Type"] = "text/xml"
render "wiki/rss_feed"
end
def rss_with_headlines
@hide_description = true
rss_with_content
end
def list
parse_category
set_menu_pages
@pages_by_name = @pages_in_category.by_name
@page_names_that_are_wanted = @pages_in_category.wanted_pages
@pages_that_are_orphaned = @pages_in_category.orphaned_pages
if @req.query['Action']
redirect_action 'list/' if web.check_pass_on_edit and not authenticate
case @req.query['Action']
when 'Delete' # Handle page deletion
wiki.delete_page(web_address, @req.query['sel_page_name'])
redirect_action "list/"
when 'Create' # Handle page creation
redirect_show @req.query['newpage']
when 'Rename' # Handle page rename
wiki.rename_page(web_address, @req.query['sel_page_name'], @req.query['newpage'])
redirect_action "list/"
end
end
end
def export_html
file_name = "#{web.address}-html-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}.zip"
file_path = EXPORT_DIRECTORY + file_name
export_pages_to_zip_file(file_path) unless FileTest.exists?(file_path)
send_export(file_name, file_path)
end
def export_markup
file_name = "#{web.address}-markup-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}.zip"
file_path = EXPORT_DIRECTORY + file_name
export_markup_to_zip_file(file_path) unless FileTest.exists?(file_path)
send_export(file_name, file_path)
end
def export_pdf
file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M')}"
file_path = EXPORT_DIRECTORY + file_name
export_web_to_tex(file_path + ".tex") unless FileTest.exists?(file_path + ".tex")
convert_tex_to_pdf(file_path + ".tex")
send_export(file_name + ".pdf", file_path + ".pdf")
end
def export_tex
file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M')}.tex"
file_path = EXPORT_DIRECTORY + file_name
export_web_to_tex(file_path) unless FileTest.exists?(file_path)
send_export(file_name, file_path)
end
def edit_web #{{{
parse_category
set_mm_options
@snapshot_interval = MadeleineService.snapshot_interval_hours
end #}}}
def update_web
redirect_show("HomePage") unless wiki.authenticate(@params["system_password"])
set_mm_options
wiki.update_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['mind_map_size'],
@params['symbols_map'],
@params['links_map'],
@params['snapshots_interval'],
@params['enable_dclick_edit'],
@params['check_pass_on_edit'] == 'each_edit',
@prog, @graph_type, @missing, @show_authors, @show_leaves, @selected_categories
)
redirect_show("HomePage", @params["address"])
end
def administrate
@logger.info "Taking administrative action: #{@params['action']}"
if wiki.authenticate(@params['system_password'])
case @params['action']
when 'Delete Orphan Pages'
wiki.remove_orphaned_pages(web_address)
when 'Clear Render Cache'
clear_render_cache true
when 'Force Data Snapshot'
MadeleineService.take_snapshot
when 'Clean Storage'
MadeleineService.clean_old_snapshots
end
@message = 'Operation succeeded'
redirect_action 'edit_web/'
else
redirect_show 'HomePage'
end
end
FAR_FUTURE = Date.new(4000,1,1).freeze
TODO_RE = %r{TODO(?:.*)?: (.*?)}
def todo #{{{
parse_category
set_menu_pages
@context = @params['context']
@sort_order = @params['sort_order']
# the clear_render_cache hack to make sure we don't have old-style rendered
# todos is no longer needed - can go thru the edit web to explicitely clear the cache.
@todo_items = analyse_rendered_todo_items @pages_in_category.by_name
@bliki_todo_items = analyse_rendered_todo_items web.bliki.values
@context_links = @todo_items.clone.update(@bliki_todo_items).map { |page, items|
# 'items' contain the full 'todo' info
items.map { |item| item.context }
}.flatten.compact.uniq.reject { |c| c.nil? || c.empty? }.map { |context|
@context == context ?
"[#{context}]" :
"#{context}"
}
@todo_items = sort_and_filter_todo_items @todo_items, @sort_order, @context
@bliki_todo_items = sort_and_filter_todo_items @bliki_todo_items, @sort_order, @context
end #}}}
def analyse_rendered_todo_items pages
items = Hash.new { Array.new }
pages.each do |page|
if page.has_todos?
# Page has todo items. Get the rendered version (marked-up and with links):
# I specifically don't use the todo chunkss because I want the fully marked-up
# text of the item.
content = page.revisions.last.display_content
items[page] = content.scan(TODO_RE).map { |match|
RenderedTodo.new match[2], (match[0].empty? ? [] : match[0].split(',') ), (Date.parse(match[1]) rescue FAR_FUTURE)
}
end
end
items
end
def sort_and_filter_todo_items items, sort_order, context
case sort_order
when 'due_date'
result = items.map { |page, todos|
# sort the to items themselves
[page, todos.sort_by { |i| i.due_date } ]
}.sort_by { |page, todos|
# sort the pages by the one with the most urgent todo
todos.map{ |i| i.due_date }.min
}
else # default = sort by page name
result = items.sort_by { |page, items| page.name }
end
if context
# filter the items to those that are in context
result = result.map { |page, items|
[ page, items.select { |item| item.context.include? context } ]
}.select { |page, items| not items.empty? }
end
result
end
def get_todo_display_style due_date
# default is the muted 'darkred', to prevent to many bright red
# items on one page: (See also chunks/todo.rb)
(due_date <=> Date.today) > -1 ? "todoFuture" : "todo"
end
def clear_render_cache dont_redirect=false
web.refresh_revisions
redirect_path "/#{web_address}/edit_web/" unless dont_redirect
end
def set_menu_pages #{{{
parse_category
@all_pages = web.select { true }
@menu_pages = case web.menu_type
when 'all' then @all_pages.by_name
when 'recent' then @all_pages.by_last_visited
when 'viewed' then @all_pages.by_most_viewed
when 'revised' then @all_pages.by_revision
when 'category' then web.select { |page| page.in_category?(web.menu_category) }
when 'user'
@menu_content = if Page === web.menu_content
web.menu_content.revisions.last.display_content
else
web.menu_content
end
nil
when 'linkers'
web.select { |page|
page.wiki_words.size > 0
}.sort_by { |page| page.name }
end
if web.menu_limit && @menu_pages
@menu_pages = @menu_pages[0..web.menu_limit]
end
end #}}}
def bliki #{{{
set_menu_pages
@entries = web.bliki_entries_by_date
unless @req.query['authorname'].nil? || @req.query['authorname'] == 'noselect'
@entries = @entries.select { |page|
page.authors.include?(@req.query['authorname'])
}
end
unless @req.query['regexp'].nil?
@entries = @entries.select { |page|
page.content =~ /#{@req.query['regexp']}/i
}
end
@color = web.color
@authors = web.authors
end #}}}
def mind #{{{
parse_category
set_menu_pages
set_mm_options
@pngFile = @mapFile = nil
case @graph_type
when 'normal'
@pngFile, @mapFile = web.create_mind_map(@prog, @missing,
@show_authors, @show_leaves, @selected_categories)
when 'author'
@pngFile, @mapFile = web.create_author_graph(@prog, @selected_categories)
when 'category'
@pngFile, @mapFile = web.create_category_graph(@prog,
@show_authors, @selected_categories)
end
end #}}}
def set_mm_options #{{{
if @req.query.empty?
@prog = web.mm_prog
@graph_type = web.mm_graph_type
@missing = @pages_in_category.wanted_pages if web.mm_show_missing
@show_authors = web.mm_show_authors
@show_leaves = web.mm_show_leaves
@selected_categories = web.mm_selected_categories
else
@prog = @req.query['draw_type'] || 'neato'
@graph_type = @req.query['graph_type'] || 'normal'
@missing = @pages_in_category.wanted_pages if @req.query['missing']
@show_authors = @req.query['show_authors'] == 'on'
@show_leaves = @req.query.empty? || @req.query['show_leaves'] == 'on'
@selected_categories = parse_multi_select 'selected_categs'
@selected_categories = [] if @selected_categories.include? 'all'
end
end #}}}
def parse_multi_select field #{{{
if @req.body
@req.body.split('&').map { |pair|
pair.split('=') }.select { |k,v|
k == field }.map { |k,v| v }
else
[]
end
end #}}}
def edit_menu #{{{
@menu_type = web.menu_type
@menu_content = web.menu_content
@list_limit = web.menu_limit
@list_limit += 1 if @list_limit >= -1
end #}}}
def save_menu #{{{
redirect_show("HomePage") unless wiki.authenticate(@params["system_password"])
unless @req.query['action'] == 'Cancel Update'
type = @req.query['type']
content = @req.query['content']
category = @req.query['category']
author = @req.query['author']
limit = @req.query['limit'].to_i rescue nil
limit = 20 unless limit
limit -= 1 if limit >= 0
# need to go through the WikiService to persist the command:
wiki.save_menu_pref(web_address, type, limit, content, category, Author.new(author, remote_ip))
end
# redirect to the most recently viewed page, or the home page.
if web_address
pname = begin
web.select{ true }.by_last_visited.first.name
rescue
"HomePage"
end
redirect_show pname
elsif wiki.webs.length == 1
# only one web, so go there.
redirect_show "HomePage", wiki.webs.values.first.address
else
redirect_path "/web_list/"
end
end #}}}
def get_map_img
file_name = "map.png"
file_path = File.join WikiService.storage_path, file_name
send_export(file_name, file_path, "image/png")
end
# Within a single page --------------------------------------------------------
def show
set_menu_pages
if @page = wiki.read_page(web_address, page_name)
unless page_name == 'HomePage'
# HomePage should not be in the menu as there's a link at the top.
@page.last_visited = Time.now
@page.viewed += 1
end
begin
render_action "page"
rescue => e
$stderr << e.backtrace.join("\n")
redirect_action "edit/#{CGI.escape(page_name)}?msg=#{CGI.escape(e.message)}"
end
else
if page_name
redirect_action "new/#{CGI.escape(page_name)}"
else
redirect_show "HomePage"
end
end
end
def published
if web.published then @page = wiki.read_page(web_address, page_name || "HomePage") else redirect_show("HomePage") end
end
def print
@page = wiki.read_page(web_address, page_name)
end
def tex
@page = wiki.read_page(web_address, page_name)
@tex_content = RedClothForTex.new(@page.content).to_tex
end
def pdf
page = wiki.read_page(web_address, page_name)
safe_page_name = page.name.gsub(/\W/, "")
file_name = "#{safe_page_name}-#{web.address}-#{page.created_at.strftime("%Y-%m-%d-%H-%M")}"
file_path = EXPORT_DIRECTORY + file_name
export_page_to_tex(file_path + ".tex") unless FileTest.exists?(file_path + ".tex")
convert_tex_to_pdf(file_path + ".tex")
send_export(file_name + ".pdf", file_path + ".pdf")
end
def new
redirect_show("HomePage") if web.check_pass_on_edit and not authenticate
@page_name, @author = page_name, default_author
end
def edit
@page = wiki.read_page(web_address, page_name)
if !@page.locked?(Time.now) || @params["break_lock"]
@page.lock(Time.now, default_author)
@author = default_author
render
else
render "wiki/locked"
end
end
def cancel_edit
@page = wiki.read_page(web_address, page_name)
@page.unlock
redirect_show
end
def save
if web.check_pass_on_edit and not authenticate
wiki.read_page(web_address, page_name).unlock if web.pages[page_name]
redirect_show("HomePage")
end
if web.pages[page_name]
page = wiki.revise_page(
web_address, page_name, @params["content"], Time.now,
Author.new(@params["author"], remote_ip), @params['edit_type']
)
page.unlock
else
page = wiki.write_page(
web_address, page_name, @params["content"], Time.now,
Author.new(@params["author"], remote_ip)
)
end
write_cookie("author", @params["author"], true)
redirect_show(page_name)
end
def revision
@page = wiki.read_page(web_address, page_name)
@revision = @page.revisions[@params["rev"].to_i]
end
def rollback
redirect_show("HomePage") if web.check_pass_on_edit and not authenticate
@page = wiki.read_page(web_address, page_name)
@revision = @page.revisions[@params["rev"].to_i]
end
# Bliki ----------------------------------------------------------------------
def bliki_delete
redirect_bliki if web.check_pass_on_edit and not authenticate
wiki.delete_bliki_entry(web_address, page_name)
redirect_bliki
end
def bliki_edit
redirect_bliki if web.check_pass_on_edit and not authenticate
@page = wiki.read_bliki_entry(web_address, page_name)
if !@page.locked?(Time.now) || @params["break_lock"]
@page.lock(Time.now, default_author)
@author = default_author
render
else
redirect_path "#{web_address}/locked"
end
end
def cancel_bliki_edit
@page = wiki.read_bliki_entry(web_address, page_name)
@page.unlock if @page
redirect_bliki
end
def bliki_save
redirect_bliki if web.check_pass_on_edit and not authenticate
pname = page_name || @params["pagename"]
if web.bliki[pname]
page = wiki.revise_bliki_entry(web_address, pname, @params["content"], Time.now, @params["author"])
page.unlock
else
page = wiki.write_bliki_entry(web_address, pname, @params["content"], Time.now, @params["author"])
end
write_cookie("author", @params["author"])
redirect_bliki
end
def bliki_revision
parse_category
set_menu_pages
@page = wiki.read_bliki_entry(web_address, page_name || @params['pagename'])
@revision = @page.revisions[@params["rev"].to_i] || @page.revisions.last
end
def rollback_bliki
redirect_bliki if web.check_pass_on_edit and not authenticate
@page = wiki.read_bliki_entry(web_address, page_name)
wiki.rollback_bliki_entry(web_address, page_name, @params["rev"].to_i, Time.now)
redirect_bliki
end
# ----------------------------------------------------------------------------
protected
def before_action
if in_a_web? && !authorized?(web_address) && !%w( login authenticate published ).include?(action_name)
redirect_action("login")
return false
elsif in_a_web?
@web, @page_name, @action_name = web, page_name, action_name
end
end
def action_name
if in_a_web?
request_path[1]
elsif action_methods.include?(request_path[0])
request_path[0]
else
"index"
end
end
def redirect_show(page = @page.name, web = web_address)
redirect_path "/#{web}/show/#{CGI.escape(page)}"
end
def redirect_bliki
redirect_path "/#{web_address}/bliki/"
end
def redirect_action(action, web = web_address)
redirect_path "/#{web}/#{action}"
end
def link_to_bliki(entry, web = web_address) #{{{
"#{entry.name}"
end #}}}
private
def wiki
WikiService.instance
end
def web
wiki.webs[web_address]
end
def in_a_web?
request_path.length > 1
end
def web_address
request_path[0]
end
def page_name
CGI.unescape(request_path[2]) if request_path[2]
end
def authorized?(web_address)
web.nil? ||
web.password.nil? ||
(read_cookie(web_address) && read_cookie(web_address) == web.password) ||
password_check(@params["password"])
end
def default_author
read_cookie("author") || "AnonymousCoward"
end
def password_check(password)
web && @params["password"] == web.password && write_cookie(web_address, @params["password"])
end
def export_pages_to_zip_file(zip_file_path)
Zip::ZipOutputStream.open(zip_file_path) do |zos|
web.select.by_name.each do |@page|
zos.put_next_entry(@page.name + ".html")
zos.puts(template_engine("print").result(binding))
end
zos.put_next_entry("index.html")
zos.puts('
')
end
end
def export_markup_to_zip_file(zip_file_path)
Zip::ZipOutputStream.open(zip_file_path) do |zos|
web.select.by_name.each do |page|
zos.put_next_entry(page.name + ".#{web.markup}")
zos.puts(page.content)
end
end
end
def export_web_to_tex(file_path)
@web_name = web.name
@tex_content = table_of_contents(web.pages["HomePage"].content.dup, render_tex_web)
File.open(file_path, "w") { |f| f.write(template_engine("tex_web").result(binding)) }
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 export_page_to_tex(file_path)
tex
File.open(file_path, "w") { |f| f.write(template_engine("tex").result(binding)) }
end
def convert_tex_to_pdf(tex_path)
`cd #{File.dirname(tex_path)}; pdflatex --interaction=scrollmode '#{File.basename(tex_path)}'`
end
def truncate(text, length = 30, truncate_string = "...")
if text.length > length then text[0..(length - 3)] + truncate_string else text end
end
def render_markup_help
if web
markup = web.markup
markup = 'markdown' if markup.to_s =~ /markdown/
sub_template("#{markup}_help")
else
''
end
end
def send_export(file_name, file_path, content_type = "application/zip")
@res["Content-Type"] = content_type
@res["Content-Disposition"] = "attachment; filename=#{file_name}"
@res["Content-Length"] = File.size(file_path)
File.open(file_path, "rb") { |f| @res.body = f.read }
end
def template_engine(template_name)
ERB.new(IO.readlines(action_template_path(template_name)).join)
end
def remote_ip
$stderr << "#{@req.meta_vars['HTTP_X_FORWARDED_FOR']} || #{@req.meta_vars['REMOTE_ADDR']}"
@req.meta_vars["HTTP_X_FORWARDED_FOR"] || @req.meta_vars["REMOTE_ADDR"]
end
end
# jEdit :folding=indent:collapseFolds=2: