blob: 723a36fe529e2699da1cbd0d771e83b8cde6e71a [file] [log] [blame]
# == Schema Information
# Schema version: 1
#
# Table name: pages
#
# id :integer(11) not null, primary key
# rel_path :string(767) default(), not null
# presentation_name :string(500) default(), not null
# content_type :string(100) default(), not null
# filename :string(250) default(), not null
# versions_count :integer(11) default(0), not null
# comments_count :integer(11) default(0), not null
# created_on :datetime
# updated_on :datetime
# body_tag :string(1000)
# treebrowser_tag :string(1000)
# copyright_tag :string(1000)
# text :text
#
require_dependency "search_system"
# A page is a wikifiable HTML file created using EPF.
#
# More information:
# * {EPF Wiki Data model}[link:files/doc/DATAMODEL.html]
#--
# # Info about Ruby regular expressions: the last i in a pattern is a modifier,
# for case insensitive matches, same as the last m, for multiline matches
# see for more info about regular expressions
# Ruby_Syntax[http://www.ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html]
#
#++
#--######################################################################
# Copyright (c) 2006 LogicaCMG
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
#
# Onno van der Straaten:: initial implementation
#++######################################################################
# {Copyright (c) 2006 LogicaCMG}[link:files/COPYRIGHT.html]
class Page < ActiveRecord::Base
has_and_belongs_to_many :baselines #, :dependent => :destroy TODO: causes an error Unknown key(s)
has_and_belongs_to_many :sites #, :dependent => :destroy TODO: causes an error Unknown key(s)
has_many :versions#, :dependent => :destroy # NOTE: this will use RoR callbacks, :delete_all won't, it will just do a MySQL delete
has_many :checkouts#, :dependent => :destroy
has_many :comments#, :dependent => :destroy
has_many :difference_analysis_items#, :dependent => :destroy
has_many :notifications#, :dependent => :destroy
validates_presence_of :rel_path, :presentation_name, :content_type, :filename
validates_length_of :filename, :maximum => 250
validates_length_of :content_type, :maximum => 100
validates_length_of :presentation_name, :maximum => 500
validates_length_of :rel_path, :maximum => 250
# For creating a new page, use to specify the source version of the template or page
# NOTE: depending where it is used (view or model) this stores an Id or and object.
attr_accessor :source_version
# For creating a new page, use to specify the user
attr_accessor :user
# For creating a new page, use to specify the site
attr_accessor :site
# When creating a new page, use to supply a note for creating the version
attr_accessor :note
make_searchable [:presentation_name, :text]
# the following HTML fragment is added to each wikifiable HTML file
IFRAME_FRAGMENT = ['<!-- epfwiki iframe start -->','<script language="JavaScript">',
'if (location.protocol == "http:") {',
'aURL = "http://" + location.host + "/toolbar/show?url=" + document.location.href;',
'document.write(" <div id=\"toolbar\">\n");',
'document.write(" <iframe width=\"250\" height=\"700\" frameborder=\"0\" src=\"" + aURL + "\" frameborder=\"0\" scrolling=\"auto\" ALLOWTRANSPARENCY=\"TRUE\">\n");',
'document.writeln(" </div>");','}','</script>','<!-- epfwiki iframe end -->'].join("\n")
# Used to replace treebrowser.js from HTML files because it chrashes the HTML editor
TREEBROWSER_PATTERN = /<script ( )*src( )*=(")?(.\/)(..\/)*scripts\/treebrowser.js.*<\/script>/i
# Placeholder for TREEBROWSER_PATTERN , so we can easily place it back in the file
TREEBROWSER_PLACEHOLDER = "<!-- treebrowser tag -->"
# Used to remove onload event from HTML files because it chrashed the HTML Editor
BODY_TAG_PATTERN = /<body.*>/i
# Used to add the HTML fragment that effectivly wikifies the HTML file
BODY_CLOSING_TAG_PATTERN = /<\/body>/i
# Used to remove HTML fragment that wikifies the HTML file so it can be edited.
IFRAME_PATTERN = /<\!-- epfwiki iframe start -->.*<\!-- epfwiki iframe end -->/im
# Used to fix some layout problems with the 'horizontal rule'
SHIM_TAG_PATTERN = /images\/shim.gif(")?( )*\/?>.?( )*?.?( )*?<\/td>/im
# Used to fix some layout problems with the 'horizontal rule'
SHIM_TAG = "images/shim.gif\"></td>"
# Used to replace copyright notice from the file so it can be edited.
COPYRIGHT_PATTERN = /<p>( )*?(.)?( )*?copyright(.)*?<\/p>/im
# Placeholder for copyright notice we find with COPYRIGHT_PATTERN
COPYRIGHT_PLACEHOLDER = "<!-- copyright statement -->"
TITLE_PATTERN = /<title>(.)*<\/title>/i
HEAD_PATTERN = /<head>(.)*<\/head>/im
TITLE_START = /<title>/i
TITLE_END = /<\/title>/i
TITLE2_PATTERN = /class="pageTitle">(.)*<\/td>/
TITLE2_START = /class="pageTitle">/i
TITLE2_END = /<\/td>/
ELEMENT_TYPE_PATTERN = /<meta(.)*element_type(.)*>/i
#--
# FIXME R1 filenames sometimes contain & characters, for instance configuration_&_change_management.
# If a file cannot be found using find_or_new we should look it for it in the directory?
#
# TODO R? new table element_type so that we can count, show pages per element_type
#
# #find_or_new can cause an errors in the console UnicodeEncodeError: 'ascii' codec can't encode character u'\u2122' in position 0: ordinal not in range(128)
# This is the case if Python isn't configured correctly, see install notes
#++
def self.find_or_new(params, theSite)
logger.debug("Finding page with relative path #{params[:rel_path]}")
raise 'Version files cannot be manipulated by users' if params[:rel_path].upcase.index("_EPFWIKI_")
page = Page.find_by_rel_path(params[:rel_path])
if page
logger.info("Page with relative path #{params[:rel_path]} found in site #{theSite.title}")
else
page = Page.new
page.rel_path = params[:rel_path]
fileContents = page.html(theSite)
page.presentation_name = page.title_from_file(theSite)
page.content_type = Page.meta_tag_content_type(fileContents)
page.filename = File.basename(File.expand_path(params[:rel_path], theSite.path))
html = page.html(theSite)
page.text = html2markdown(page.path(theSite))
tag = html
tag =~ BODY_TAG_PATTERN
page.body_tag = $&
html = IO.readlines(page.path(theSite)).join
tag2 = html
tag2 =~ TREEBROWSER_PATTERN
page.treebrowser_tag = $&
page.copyright_tag = COPYRIGHT_PATTERN.match(html).to_s
end
return page
end
# Method #prepare_for_edit is used to prepare the file for editing in
# the HTML editor that runs in the browser:
# 1. onload event is removed from the body element
# 2. Javascript lib treebrowser.js that chrashes the editor is replaced by a placeholder comment tag
# 3. the EPF iframe element is removed
# 4. the copyright_tag is replaced by a placeholder tag
#
# See also #find_or_new where content that is removed here, was stored.
#--
# TODO: use tidy_file here too?
# TODO: move self.page attributes here?
#++
def self.prepare_html_for_editing(html)
html = html.gsub(BODY_TAG_PATTERN, '<body>') # 1
treebrowser_tag = html
treebrowser_tag =~ TREEBROWSER_PATTERN
html = html.gsub(TREEBROWSER_PATTERN, TREEBROWSER_PLACEHOLDER) # 2
html = html.gsub(IFRAME_PATTERN, '') # 3
html = html.gsub(COPYRIGHT_PATTERN, COPYRIGHT_PLACEHOLDER) # 4
return html
end
# Method #enhance_file enhances a file if it hasn't been enhanced yet.
# * replace body-tag with iframe
# * add Javascript libs and Wiki styles
# * change table width from 100% to 99% to get rid of the vertical scrollbar
def self.enhance_file(path)
if IO.readlines(path).join.index('epfwiki iframe end')
logger.info("File skipped (already enhanced): #{path}")
else
logger.info("Enhancing file: " + path)
html = IO.readlines(path).join #("\n")
html = html.gsub(BODY_CLOSING_TAG_PATTERN, IFRAME_FRAGMENT + "</body>\n")
html = html.gsub("width=\"100%\"", "width=\"99%\"")
file = File.new(path, "w")
file.puts(html)
file.close
end
end
def self.meta_tag_content_type(html)
return ELEMENT_TYPE_PATTERN.match(html).to_s.downcase.gsub("meta", "").gsub("name=", "").gsub("content=", "").gsub("element_type", "").gsub("\"", "").gsub("<","").gsub(">", "").strip.capitalize
end
# Method #sites_with_versions returns collection of sites with versions
def sites_with_versions
return self.versions.collect{|aVersion| aVersion.site}.uniq
end
# Method #baselines_with_versions returns collection of baselines with versions
def baselines_with_versions
return self.versions.collect{|aVersion| aVersion.baseline}.uniq
end
# #meta_tag_content_type extracts the meta_tag 'content type' from the HTML
def meta_tag_content_type(site)
return Page.meta_tag_content_type(self.html(site))
end
# what is or should be the #path to the Page in a Site. This can only be determined in context of a Site.
def path(site)
return site.path + '/' + self.rel_path
end
def html(site)
return IO.readlines(self.path(site)).join
end
# #title_from_file reads the value of the title element from the HTML file.
# This should correspond with the "presentation_name" column.
# Example: TaskDescriptor: Review Change Requests
# See also #title_2file
def title_from_file(site)
title = "N.A."
self.html(site) =~ TITLE_PATTERN
title = $&.gsub(TITLE_START,'').gsub(TITLE_END,'')
return title
end
# #title_from_file updates the title element in the HTML file based on the value of <tt>presentation_name</tt> column. These should be equal.
# See also #title_from_file
#--
# TODO not used, remove?
#++
def title_2file(site)
html = self.html(site)
html.gsub()
lines = IO.readlines(path(theSite)) #.join("\n") # this contains #{title}
lines.each do |aLine|
if aLine.index("<title>")
strFront, aLine = aLine.split("<title>")
title, strBack = aLine.split("</title>")
end
end
end
def versions_in_site(site)
return Version.find(:all, :conditions => ['site_id=? and page_id=?', site.id, id], :order=> 'created_on ASC')
end
def comments_in_site(site)
return Comment.find(:all, :conditions => ['site_id=? and page_id=?', site.id, id], :order=> 'created_on ASC')
end
def checkout(site)
return Checkout.find_by_site_id_and_page_id(site.id, self.id)
end
# return collection of wikis that allow new checkout of this page
def wikis_4checkout
return Site.find_wikis - self.checkouts.collect {|checkout| checkout.site}
end
def before_validation_on_create
if !self.source_version.nil?
logger.info('New page based on a source version (of another page or template)')
logger.error('Supplied filename will be overwritten') if !self.filename.blank?
raise "User can't be blank" if self.user.blank?
self.filename = self.presentation_name.downcase.delete("&+-.\"/\[]:;=,").tr(" ","_") + ".html" if !self.presentation_name.blank?
if self.source_version.template?
self.rel_path = File.expand_path(self.filename, self.site.new_files_dir).gsub(self.site.path + '/','')
logger.info("New page based on a template, rel_path is #{self.rel_path}")
else
self.rel_path = File.expand_path(self.filename, File.dirname(self.source_version.path)).gsub(self.site.path + '/','')
logger.info("New page based on another page, rel_path is #{self.rel_path}")
#self.source_version.page.rel_path.gsub(self.source_version.page.filename,self.filename) TODO remove
end
self.content_type = self.source_version.page.meta_tag_content_type(self.source_version.site)
self.body_tag = self.source_version.page.body_tag
self.treebrowser_tag = self.source_version.page.treebrowser_tag
self.copyright_tag = self.source_version.page.copyright_tag
#self.text = self.source_version.page.text
unless File.exist?(self.path(self.site)) || !Page.find_by_rel_path(self.rel_path).nil?
self.checkouts << Checkout.new(:user => self.user, :page => self, :site => self.site, :source_version => self.source_version, :note => self.note)
self.sites << site
end
end
end
def validate_on_create
logger.info('validate_on_create')
if !self.source_version.nil?
errors.add(:rel_path, "already used; can\'t create another file as a file with path #{self.path(self.site)} already exists") if File.exist?(self.path(self.site))
errors.add(:rel_path, "already used; can\'t create another page with relative path #{self.rel_path}") if !Page.find_by_rel_path(self.rel_path).nil?
end
end
end