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"] rex = /#{@query}/i @results = web.select { |page| rex.match(page.name) or rex.match(page.content) } @bliki_results = web.bliki.values.select do |entry| rex.match(entry.name) or rex.match(entry.content) end @results.length == 1 ? redirect_show(@results.first.name) : render 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 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['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 remove_orphaned_pages if wiki.authenticate(@params["system_password"]) wiki.remove_orphaned_pages(web_address) redirect_action "list/" 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'] clear_render_cache # hack to make sure we don't have old-style rendered todos. @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 do_redirect=false web.refresh_revisions redirect_show 'HomePage' if do_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 'user' then @menu_content = web.menu_content.revisions.last.display_content; nil when 'category' then web.select { |page| page.in_category?(web.menu_category) } 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' # TODO: fix handling of multiple-select for whole application @selected_categories = @req.body.split('&').map { |pair| pair.split('=') }.select { |k,v| k == 'selected_categs' }.map { |k,v| v } if @req.body @selected_categories ||= [] @selected_categories = [] if @selected_categories.include? 'all' 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: