# This module is used to extend each erb page to provide helper methods module ErbHelper def url( name, view = 'view' ) "#{$SETTINGS[:url]}/#{view}/#{name}" end def file( name ) "#{$SETTINGS[:url]}/attachment/#{name}" end end # This class records all the links between pages class Links def initialize @links = Hash.new end def links_from( page ) ; return @links[ page ] ; end def set_links_from( page, linkarray ) page.links_lock.synchronize do page.links_from = @links[ page ] = linkarray.uniq set_links_to( page ) end page.links_from.each { |linked_page| linked_page.links_lock.synchronize do set_links_to( linked_page ) end } end private def set_links_to( thispage ) linksto = Array.new @links.each do | pagefrom, pagesto | next if pagefrom == thispage next if linksto.include? pagefrom linksto << pagefrom if pagesto.include? thispage end thispage.links_to = linksto.sort end end class BruteMatch IGNORE_CASE = true def initialize @matches = Hash.new @titles = Array.new #sorted array for speed end def []( title ) @matches[ lower_case(title) ] end alias :object_for :[] def delete( title ) @matches.delete( title ) update_titles end # Use this to add a string to match and an associated object to return # if an object is matched. def []=( title, object ) @matches[lower_case(title)] = object update_titles end def match( text ) @titles.each do |title| regexp = /\b#{Regexp.escape(title)}\b/i page = @matches[ title ] text.gsub!( regexp ) { |match| yield match, page } end text end private def update_titles @titles = @matches.keys.sort_by { |title| title.length }.reverse end def lower_case( text ) IGNORE_CASE ? text.downcase : text end end # This adds some extra match types to (a bodged version of) RedCloth # # Specifically: # * Inserting other pages # * Square bracketed wiki links # * Automaticlly links anytime the title of another page appears in the text # * Automatically links things that look like email addresses and urls class WikiRedCloth < RedCloth RULES = [:refs_soks_bracketed_link, :textile, :markdown, :inline_soks ] def initialize( wiki, page, string ) @wiki, @page = wiki, page @internal_links_from_page = [] super(insert_sub_strings( string.dup ), [ :no_span_caps]) self.hard_breaks = true if $SETTINGS[:redcloth_hard_breaks] end def to_html super( *RULES ) end def inline_soks( text ) hide_html_links text hide_html_tags text inline_soks_external_link text inline_soks_automatic_link text unhide text @wiki.links.set_links_from( @page, @internal_links_from_page ) text end private def hide_html_links( text ) text.gsub!(//i) { |m| hide m } end def hide_html_tags( text ) text.gsub!(/<.*?>/m) { |m| hide m } end def unhide( text ) hidden.each_with_index do |r, i| text.gsub!( / --!!#{ i + 1 }!!-- /, r ) end text end def hidden @hidden ||= [] end def hide( text ) hidden << text " --!!#{hidden.length}!!-- " end def insert_sub_strings( text, count = 0 ) return text if count > 5 # Stops us getting locked into a cycle if people mess up the insert text.gsub!(/\[\[\s*(insert (.+?))\s*\]\]/i) do |m| unless @wiki.exists? $1 # So we don't accidentlaly match a page whose name starts 'insert' inserted_page = @wiki.page( $2 ) if @wiki.exists? inserted_page.name @internal_links_from_page << inserted_page inserted_page.is_inserted_into( @page ) end insert_sub_strings( "#{inserted_page.textile}\n", count + 1 ) else m end end text end def inline_soks_external_link( text ) text.gsub!(/http:\/\/\S*\w\/?/i) { |m| link m } text.gsub!(/https:\/\/\S*\w\/?/i) { |m| link m } text.gsub!(/www\.\S*\w\/?/i) { |m| link( "http://#{m}", m) } text.gsub!(/[A-Za-z0-9.]+?@[A-Za-z0-9.]*[A-Za-z]/) { |m| link( "mailto:#{m}", m) } end def refs_soks_bracketed_link( text ) text.gsub!(/\[\[\s*(.*?)\s*(|=>\s*(.*?)\s*)\]\]/) do |m| title, pagename = $1, $3 pagename ||= title case pagename when /^www\./i ; link("http://#{pagename}", title ) when /[A-Za-z0-9.]+?@[A-Za-z0-9.]+/ ; link("mailto:#{pagename}",title) when /^http:\/\//i ; link(pagename,title) else ; wiki_link( pagename, title ) end end end def inline_soks_automatic_link( text ) @wiki.rollingmatch.match( text ) { |title, page| wiki_link( page.name, title, 'automatic' ) } end def wiki_link( pagename, title, css_class = nil ) if @wiki.exists? pagename @internal_links_from_page << @wiki.page( pagename ) css_class ||= '' end link( url_for(pagename), title, css_class || 'missing') end def link( url, title = url, css_class = '' ) shelve "#{title}" end def url_for( pagename, view = 'view' ) view, pagename = $1, $2 if pagename =~ /\/(\w+)\/(.*)/ "#{$SETTINGS[:url]}/#{view}/#{pagename}" end end class View include ErbHelper attr_reader :rollingmatch, :links def initialize( wiki, name ) @wikiname = name @rollingmatch, @links, @redcloth_cache, @erb_cache = BruteMatch.new, Links.new, Hash.new, Hash.new @wiki = wiki wiki.watch_attentively_for( :page_revised ) { |event,page,revision| refresh_redcloth( page ) } end def view( pagename, view = 'view', person = nil ) page = @wiki.page pagename renderedview = redcloth( page ) content_of_page = html( page.class, view, binding ) if should_frame? view frame_erb.result binding else content_of_page end end def find( pagename ) return view( pagename ) if @wiki.exists?( pagename ) view = 'find' search_term = /#{pagename}/i title_results = @wiki.select { |name,page| name=~ search_term } text_results = @wiki.select { |name,page| page.content=~ search_term } content_of_page = html( Page, 'search_results', binding ) page = nil frame_erb.result binding end def revise( pagename, content, person, newpagename = pagename ) if @wiki.exists?( pagename ) && (newpagename != pagename) @wiki.revise( pagename, "#{$MESSAGES[:content_moved_to]} [[#{newpagename}]]", person ) @wiki.revise( newpagename, "#{$MESSAGES[:content_moved_from]} [[#{pagename}]]", 'AutomaticPageMover' ) @wiki.revise( newpagename, content, person ) else @wiki.revise( newpagename, content, person ) end end def move( oldpagename, person, newpagename ) unless newpagename == oldpagename @wiki.revise( newpagename, "#{$MESSAGES[:content_moved_from]} [[#{oldpagename}]]", 'AutomaticPageMover') @wiki.revise( newpagename, @wiki.page( oldpagename ).content, person) @wiki.revise( oldpagename, "#{$MESSAGES[:content_moved_to]} [[#{newpagename}]]", person) end end def rollback( pagename, revision, person ) @wiki.rollback( pagename, revision, person ) end def delete( pagename, person ) @wiki.revise( pagename, $MESSAGES[:page_deleted], person ) end def refresh_redcloth( page ) $LOG.info "Refreshing #{page}" @redcloth_cache[page] = "" if page.textile.strip == "" @redcloth_cache[ page ] = WikiRedCloth.new( self, page, page.textile ).to_html end def redcloth( page ) @redcloth_cache[page] = "" if page.textile.strip == "" @redcloth_cache[ page ] ||= WikiRedCloth.new( self, page, page.textile ).to_html end def clear_redcloth_cache( page = :all_pages ) ( page == :all_pages ) ? @redcloth_cache.clear : @redcloth_cache.delete( page ) end def html( klass, view, _binding ) @erb_cache.clear if $SETTINGS[:reload_erb_each_request] ( @erb_cache[ path_for( klass, view ) ] ||= ERB.new( IO.readlines( erb_filename( klass, view ) ).join ) ).result( _binding ) end def erb_filename( klass, view ) $LOG.info "Looking for #{path_for( klass, view)}" until File.exists?( path_for( klass, view ) ) if klass.superclass klass = klass.superclass else raise WEBrick::HTTPStatus::NotFound end end path_for( klass, view ) end def path_for( klass, view ) "#{$SETTINGS[:root_directory]}/views/#{klass}_#{view}.rhtml" end def should_frame?( view ) return true unless $SETTINGS[:dont_frame_templates].include? view.downcase end def frame_erb @frame_erb = nil if $SETTINGS[:reload_erb_each_request] @frame_erb ||= load_frame_erb end def load_frame_erb if File.exists? "#{$SETTINGS[:root_directory]}/views/frame.rhtml" ERB.new( IO.readlines( "#{$SETTINGS[:root_directory]}/views/frame.rhtml" ).join ) else ERB.new( "<%= content_of_page %>" ) end end # CGI and FCGI can't access this global directly, so call through here def settings $SETTINGS end def method_missing( method, *args, &block ) @wiki.send( method, *args, &block ) end end