| class Version < ActiveRecord::Base |
| |
| belongs_to :user |
| belongs_to :wiki, :foreign_key => 'wiki_id' # TODO not necessary? |
| belongs_to :page |
| has_one :checkout |
| belongs_to :source_version, :class_name => 'Version', :foreign_key => 'version_id' |
| belongs_to :reviewer, :class_name => 'User', :foreign_key => 'reviewer_id' |
| has_many :child_versions, :class_name => 'Version', :foreign_key => 'version_id' |
| has_many :comments |
| |
| before_destroy :delete_file |
| |
| attr_accessor :type_filter # used for selection filter |
| |
| # NOTE: it is not possible to use 'update' below, this is reserved by ActiveRecord. |
| # If you use it, the record won't save |
| belongs_to :baseline_update, :class_name => 'Update', :foreign_key => 'update_id' |
| |
| validates_presence_of :user, :wiki, :page |
| |
| BODY_PATTERN = /<body.*<\/body>/m |
| LINKS_PATTERN = /<a.*?href=".*?".*?<\/a>/ |
| HREF_PATTERN = /href="(.*?)"/ |
| |
| DIFF_STYLE = ["<style type=\"text/css\">", |
| "ins { text-decoration: none; background-color: #ff0; color: #057508; } |
| del { text-decoration: line-through; color: #f00; } |
| |
| div>ins, ul>ins, div>del, ul>del, body>ins, body>del { display: block; }", |
| "</style>"].join("\n") |
| |
| def previous_version |
| case self.version |
| when 0 then nil |
| when nil # TODO when the version no is nil, it is a checked out version |
| if true |
| self.page.current_version |
| end |
| else Version.find(:first, :conditions => ['page_id=? and version=?', self.page_id, self.version - 1]) |
| end |
| end |
| |
| # TODO this doesn't add value |
| def baseversion |
| return Version.find(:first, :conditions => ['wiki_id = ? and page_id=? and version=0', |
| self.wiki_id, self.page_id]) |
| end |
| |
| def current_version |
| return self.page.current_version |
| end |
| |
| # return the latest version of page in a site |
| def self.find_latest_version(site, page) |
| return Version.find(:first ,:order => 'version DESC', :conditions => ["page_id=? and wiki_id =?", page.id, site.id]) |
| end |
| |
| # return the latest version based on a version |
| # TODO use page.last_version |
| def latest_version |
| return Version.find(:first ,:order => 'version DESC', :conditions => ["page_id=? and wiki_id =?", self.page, self.wiki]) |
| end |
| |
| # Returns relative path of a version in folder 'public', |
| # the column rel_path stores the relatieve path in a Site folder |
| def rel_path_root |
| return self.path.gsub("#{ENV['EPFWIKI_ROOT_DIR']}#{ENV['EPFWIKI_PUBLIC_FOLDER']}/", '') |
| end |
| |
| def html |
| IO.readlines(self.path).join |
| end |
| |
| # Create the tmp diff source file, clean it using Tidy and prepare for use with XHTMLDiff |
| def html4diff(h = nil) |
| logger.info("Create tmp diff source file #{self.path_to_tmp_diff_html} using #{self.path}") |
| h = IO.readlines(self.path).join if h.nil? |
| html = Nokogiri.HTML(h) |
| xhtml = html.to_xhtml |
| h = BODY_PATTERN.match(xhtml.to_s)[0] |
| h = h.gsub(/<[\/]{0,1}(tbody){1}[.]*>/, '') |
| h = h.gsub(Page::TREEBROWSER_PATTERN,'') |
| h = h.gsub(Page::TREEBROWSER_PLACEHOLDER,'') |
| h = h.gsub(Page::TREEBROWSER_PATTERN,'') |
| h = h.gsub(Page::TREEBROWSER_PLACEHOLDER,'') |
| h = h.gsub(' ','').gsub('nowrap="nowrap"','').gsub('width="100%"','width="99%"')#.gsub(' ','') |
| h = h.gsub(/guid="(.*?)"/, '') # v0 diffs |
| h = h.gsub('<div id="breadcrumbs"></div>','') |
| h = h.gsub('class="sectionTable" border="0" cellspacing="0" cellpadding="0"', 'border="0" cellspacing="0" cellpadding="0" class="sectionTable"') |
| h = h.gsub('cellspacing="0" cellpadding="0"', 'cellpadding="0" cellspacing="0"') # v0: TinyMCE changes sort |
| h = h.gsub('<p></p>','') # TinyMCE adds empty p element |
| |
| |
| h = h.gsub(/title="(.*?)"/, '') |
| logger.debug("html4diff #{self.path}: ") |
| file = File.new(self.path_to_tmp_diff_html, 'w') |
| file.puts(h) |
| file.close |
| h |
| end |
| |
| # Create diff results using XHTMLDiff |
| def xhtmldiff(from_version) |
| content_to = "<div>\n" + self.html4diff + "\n</div>" |
| content_from = "<div>\n" + from_version.html4diff + "\n</div>" |
| |
| diff_doc = REXML::Document.new |
| diff_doc << (div = REXML::Element.new 'div') |
| hd = XHTMLDiff.new(div) |
| |
| parsed_from_content = REXML::HashableElementDelegator.new(REXML::XPath.first(REXML::Document.new(content_from), '/div')) |
| parsed_to_content = REXML::HashableElementDelegator.new(REXML::XPath.first(REXML::Document.new(content_to), '/div')) |
| Diff::LCS.traverse_balanced(parsed_from_content, parsed_to_content , hd) |
| diffs = '' |
| diff_doc.write(diffs, -1, true, true) |
| diffs |
| end |
| |
| # Create diff file using #xhtmldiff. The file is generated if doesn't exist of if one of the versions is checked out. |
| def xhtmldiffpage(from_version, force = true) |
| p = path_to_diff(from_version) |
| if !File.exists?(p) || !from_version.checkout.nil? || !self.checkout.nil? || force |
| logger.info("Generating xhtmldiffpage #{p}") |
| diffs = xhtmldiff(from_version) |
| h = Nokogiri::HTML(page.html).to_html |
| body_tag = Page::BODY_TAG_PATTERN.match(h)[0] |
| h = h.gsub(BODY_PATTERN,body_tag + diffs + '</body>') |
| h = h.gsub('</head>',DIFF_STYLE + '</head>') |
| h = h.gsub(Page::PAGE_HEAD_SNIPPET_PATTERN, '') |
| file = File.new(p, 'w') |
| file.puts(h) |
| file.close |
| end |
| end |
| |
| # Absolute path to the diff file and paths to the intermediate (tidied, cleaned) HTML files |
| def path_to_diff(from_version) |
| return "#{ENV['EPFWIKI_ROOT_DIR']}#{ENV['EPFWIKI_PUBLIC_FOLDER']}/#{relpath_to_diff(from_version)}" |
| end |
| |
| # Relative path to the diff file |
| def relpath_to_diff(from_version) |
| return '/' + self.wiki.rel_path + '/' + self.page.rel_path + "_EPFWIKI_DIFF_V#{from_version.version}_V#{self.version}.html" |
| end |
| |
| # Path intermediate (tidied, cleaned) HTML files, prepared for XHTMLDiff |
| def path_to_tmp_diff_html |
| FileUtils.makedirs(ENV['EPFWIKI_DIFFS_PATH']) unless File.exists?(ENV['EPFWIKI_DIFFS_PATH']) |
| "#{ENV['EPFWIKI_DIFFS_PATH']}#{self.id.to_s}.html" |
| end |
| |
| # Compares hrefs to determine new or added |
| #-- |
| # Depends on covert_urls settings of TinyMCE |
| #++ |
| # TODO diff enhancement |
| #def diff_links(from_version) |
| # links = self.html.scan(LINKS_PATTERN) |
| # links_from = from_version.html.scan(LINKS_PATTERN) |
| # hrefs = links.collect {|link | HREF_PATTERN.match(link)[0]} |
| # hrefs_from = links_from.collect {|link | HREF_PATTERN.match(link)[0]} |
| # new_removed = [hrefs - hrefs_from, hrefs_from - hrefs] |
| # new_removed |
| #end |
| |
| # TODO diff enhancement |
| #def diff_img(from_version) |
| #end |
| |
| #def diff_area_links |
| #end |
| |
| def template? |
| self.wiki.title == 'Templates' |
| end |
| |
| def base_version? |
| !user_version? |
| end |
| |
| def user_version? |
| baseline_process_id.nil? |
| end |
| |
| # #version_text returns version number, site title and baseline, |
| # for example <tt>1 from OpenUP(OUP_20060721)</tt> |
| def version_text |
| if self.base_version? |
| return self.version.to_s + ' from ' + self.wiki.title + ' (' + self.baseline_process.title+ ')' |
| else |
| return self.version.to_s + ' from ' + self.wiki.title + ' (' + self.user.name + ')' |
| end |
| end |
| |
| def delete_file |
| logger.debug("Before Destroy: deleting file #{self.path}") |
| File.delete(self.path) |
| end |
| |
| # #uma_type_descr is used to retrieve the brief description for a template, see app/views/pages/new |
| def uma_type_descr |
| logger.debug("Returning uma_type_description from: #{self.inspect}") |
| if self.note.blank? || self.note == 'Automatically created' |
| match = /<table class="overviewTable".*?<td valign="top">(.*?)<\/td>/m.match(self.html) |
| if match |
| self.note = match[1] |
| self.save! |
| logger.info("Match found: #{self.note}") |
| else |
| logger.debug('No match') |
| end |
| end |
| return self.note |
| end |
| |
| end |