| # == 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 |