# -*- coding: utf-8 -*- require 'net/dav' require 'vortex_client/string_utils' require 'highline/import' require 'time' # Utilities for managing content in the web content management system Vortex. # All calls are done with the webdav protocol. module Vortex class Connection < Net::DAV # Create a new connection to Vortex. Prompts for username and password if not # supplied. Overrides Net::DAV.initialize() # # Examples: # # vortex = Vortex::Connection.new("https://www-dav.server.com",user,pass) # # vortex = Vortex::Connection.new("https://www-dav.server.com") => # Username: tiger # Password: ***** def initialize(uri, *args) @uri = uri @uri = URI.parse(@uri) if @uri.is_a? String @have_curl = false # This defaults to true in Net::DAV @handler = NetHttpHandler.new(@uri) @handler.verify_server = false # This defaults to true in Net::DAV if(args != []) @handler.user = args[0] @handler.pass = args[1] else @handler.user = ask("Username: ") {|q| q.echo = true} @handler.pass = ask("Password: ") {|q| q.echo = "*"} # false => no echo end return @handler end # Returns true if resource or collection exists. # # Example: # # vortex.exists?("https://www-dav.server.com/folder/index.html") def exists?(uri) uri = URI.parse(uri) if uri.is_a? String begin self.propfind(uri.path) rescue Net::HTTPServerException => e return false if(e.to_s =~ /404/) end return true end # Publish a document object to the web. # # Publishes a object by performing a PUT request to object.url with object.content # and then performing a PROPPATCH request to object.url with object.properties # # Example: # # vortex = Vortex::Connection.new("https://www-dav.server.com") # article = Vortex::StructuredArticle(:title=>"My title") # vortex.publish(article) def publish(object) if(object.is_a? HtmlArticle or object.is_a? HtmlEvent or object.is_a? StructuredArticle) uri = @uri.merge(object.url) # puts "DEBUG: '" + object.class.to_s + "=" + object.properties self.put_string(uri, object.content) self.proppatch(uri, object.properties) return uri.to_s else warn "Unknown vortex resource: " + object.class.to_s end end # Creates collections # # Example: # # connection = Connection.new('https://host.com') # collecion = ArticleListingCollection.new(:url => '/url') # connection.create(collection) def create(object) if(object.is_a? Collection) uri = @uri.merge(object.url) self.mkdir(uri) self.proppatch(uri, object.properties) return uri.to_s end end private # Disable Net::DAV.credentials def credentials(user, pass) end end # Gimmick the internal resource hierarchy in Vortex as class hierarchy in ruby # with resource as the root class. class Resource end # PlainFile: This is the same as 'File' in Vortex. class PlainFile < Resource # Named PlainFile so it won't get mixed up with standard File class. end # HtmlArticle: Plain HTML files with title, introduction and keywords set as WebDAV properties. # # Examples: # # article = HtmlArticle.new(:title => "Sample Title", # :introduction => "Introduction", # :body => "

Hello world

") # vortex.publish(article) class HtmlArticle < PlainFile attr_accessor :title, :introduction, :body, :filename, :modifiedDate, :publishedDate, :owner, :url, :author, :date, :tags, :picture # Create a new article of type html-article: plain html file with introduction stored as a webdav property. def initialize(options={}) options.each{|k,v|send("#{k}=",v)} end def to_s "#" end def url if(@url) @url else if(filename) filename end if(title) StringUtils.create_filename(title) + ".html" else warn "Article must have either a full url or title. " end end end def escape_html(str) new_str = str.gsub(" ","") #remove line break new_str = new_str.gsub("\"",""") #swaps " to html-encoding new_str = new_str.gsub("'","'") #swaps ' to html-encoding new_str = new_str.gsub("<","<") new_str = new_str.gsub(">",">") new_str = new_str.gsub(/'/, "\"") # Fnutter gir "not valid xml error" new_str = new_str.gsub(" ", " ") #   gir også "not valid xml error" new_str = new_str.gsub("–", "-") # Tankestrek til minustegn new_str = new_str.gsub("’","'") # Fnutt new_str = new_str.gsub("‘","'") # Fnutt new_str = new_str.gsub("“","'") # Fnutt new_str = new_str.gsub("”","'") # Fnutt new_str = new_str.gsub("”","'") # Norske gåseøyne til fnutt return new_str end def properties props = 'article' + 'article' + 'utf-8' if(@publishedDate and @publishedDate != "") if(@publishedDate.kind_of? Time) @publishedDate = @publishedDate.httpdate.to_s end props += '' + @publishedDate + '' end if(date and date != "") if(date.kind_of? Time) date = @date.httpdate.to_s end if(@publishedDate == nil or @publishedDate != "") props += '' + date + '' end props += '' + date + '' + '' + date + '' + '' + date + '' + '' + date + '' end if(picture) props += '' + picture + '' end if(title) props += '' + title + '' end if(owner) props += '' + owner + '' end if(introduction and introduction != "") props += '' + escape_html(introduction) + '' end if(author and author != "") props += '' + '' + '' + author + '' + '' + '' end if(tags and tags.kind_of?(Array) and tags.size > 0) props += '' + '' tags.each do |tag| props += "#{tag}" end props += '' end return props end def content content = '' + '' + title + '' + ' ' + '' if(body) content += body end content += '' end end # HtmlEvent: Event document. Article with location, map url, start and end dates. # # Examples: # # event = Vortex::HtmlEvent.new(:title => "Sample Event 1", # :introduction => "Sample event introduction", # :body => "

Hello world

", # :startDate => Time.now, ## "22.01.2010 12:15", # :endDate => Time.now + 60*60, ## "22.01.2010 13:00", # :location => "Forskningsveien 3B", # :mapUrl => "http://maps.google.com/123", # :tags => ["vortex","testing"], # :publishedDate => "05.01.2010 12:00") # vortex.publish(event) class HtmlEvent < PlainFile attr_accessor :title, :introduction, :body, :filename, :modifiedDate, :publishedDate, :date, :owner, :url, :tags, :startDate, :endDate, :location, :mapUrl # Create a new article of type html-article: plain html file with # introduction stored as a webdav property. def initialize(options={}) options.each{|k,v|send("#{k}=",v)} end def to_s "#" end def url if(@url) @url else if(filename) filename end if(title) StringUtils.create_filename(title) + ".html" else warn "Article must have either a full url or title. " end end end def properties props = 'event' + 'event' + 'utf-8' if(@publishedDate and @publishedDate != "") if(@publishedDate.kind_of? Time) @publishedDate = @publishedDate.httpdate.to_s end props += '' + @publishedDate + '' end if(@date and @date != "") if(@date.kind_of? Time) @date = @date.httpdate.to_s end if(@publishedDate == nil or @publishedDate != "") props += '' + date + '' end props += '' + date + '' + '' + date + '' + '' + date + '' + '' + date + '' end if(title) props += '' + title + '' end if(owner) props += '' + owner + '' end if(introduction and introduction != "") props += '' + introduction + '' end if(tags and tags.kind_of?(Array) and tags.size > 0) props += '' + '' tags.each do |tag| props += "#{tag}" end props += '' end if(@startDate and @startDate != "") if(@startDate.kind_of? Time) @startDate = @startDate.httpdate.to_s end props += '' + @startDate + '' end if(@endDate and @endDate != "") if(@endDate.kind_of? Time) @endDate = @endDate.httpdate.to_s end props += '' + @endDate + '' end if(@location and @location != "") props += '' + @location + '' end if(@mapUrl and @mapUrl != "") props += '' + @mapUrl + '' end return props end # TODO: Samme kode som i article... Bruk arv! def content content = '' + '' + title + '' + ' ' + '' if(body) content += body end content += '' end end # Vortex article stored as JSON data. # TODO: Fill out the stub. class StructuredArticle < HtmlArticle attr_accessor :title, :introduction, :content, :filename, :modifiedDate, :publishedDate, :owner, :url, :picture # Create an article # Options: # # :title => "Title" mandatory def initialize(options={}) options.each{|k,v|send("#{k}=",v)} end def url if(@url) @url else if(filename) filename end if(title) StringUtils.create_filename(title) + ".html" else warn "Article must have either a full url or title. " end end end def to_s "#" end def content json = <<-EOF { "resourcetype": "structured-article", "properties": { EOF if(body and body.size > 0) tmp_body = body # Escape '"' and line shifts so html will be valid json data. tmp_body = body.gsub(/\r/,"\\\r").gsub(/\n/,"\\\n").gsub(/\"/,"\\\"") json += " \"content\": \"#{tmp_body}\",\n" end if(author and author.size > 0) json += " \"author\": [\"#{author}\"],\n" end json += " \"title\": \"#{title}\",\n" if(introduction and introduction.size > 0) tmp_introduction = introduction tmp_introduction = tmp_introduction.gsub(/\r/,"\\\r") tmp_introduction = tmp_introduction.gsub(/\n/,"\\\n") tmp_introduction = tmp_introduction.gsub(/\"/,"\\\"") json += " \"introduction\": \"#{tmp_introduction}\",\n" end if(picture) json += " \"picture\": \"#{picture}\",\n" end json += <<-EOF "hideAdditionalContent": "false" } } EOF return json end def properties props = 'structured-article' + # 'structured-article' + 'utf-8' + 'application/json' if(@publishedDate and @publishedDate != "") if(@publishedDate.kind_of? Time) @publishedDate = @publishedDate.httpdate.to_s end props += '' + @publishedDate + '' end if(date and date != "") if(date.kind_of? Time) date = @date.httpdate.to_s end if(@publishedDate == nil or @publishedDate != "") props += '' + date + '' end props += '' + date + '' + '' + date + '' + '' + date + '' + '' + date + '' end if(picture) # props += '' + picture + '' end if(title) # props += '' + title + '' end if(owner) props += '' + owner + '' end if(introduction and introduction != "") # props += '' + escape_html(introduction) + '' end if(author and author != "") # props += '' + # '' + # '' + author + '' + # '' + # '' end if(tags and tags.kind_of?(Array) and tags.size > 0) # props += '' + # '' # tags.each do |tag| # props += "#{tag}" # end # props += '' end return props end end # Collection (folder) class Collection < Resource attr_accessor :title, :url, :foldername, :name, :introduction, :navigationTitle, :sortByDate, :sortByTitle, :owner def url if(@url) return @url end if(@foldername) return @foldername end if(@name) return @name end if(@title) return StringUtils.create_filename(title) end return "no-name" end def initialize(options={}) options.each{|k,v|send("#{k}=",v)} end def to_s "#" end def properties() # props = "collection" + # "article-listing" props = "" if(title and title != "") props += "#{title}" end if(navigationTitle and navigationTitle != "") props += "#{navigationTitle}" end if(owner and owner != "") props += "#{owner}" end return props end end # Article listing collection # # Examaple: # # collection = ArticleListingCollection(:url => 'news') # collection = ArticleListingCollection(:foldername => 'news') # collection = ArticleListingCollection(:title => 'My articles') # collection = ArticleListingCollection(:title => 'My articles', # :foldername => 'articles', # :navigationTitle => 'Read articles') class ArticleListingCollection < Collection def properties() props = super props += "article-listing" + "article-listing" return props end end class EventListingCollection < Collection def properties() props = super props += "event-listing" + "event-listing" return props end end # Utilities # # Convert norwegian date to Time object with a forgiven regexp # # TODO: Move this somewhere. # # Examples: # # t = norwegian_date('1.1.2010') # t = norwegian_date('22.01.2010') # t = norwegian_date('22.01.2010 12:15') # t = norwegian_date('22.01.2010 12:15:20') def norwegian_date(date) if /\A\s* (\d\d?).(\d\d?).(-?\d+) \s? (\d\d?)?:?(\d\d?)?:?(\d\d?)? \s*\z/ix =~ date year = $3.to_i mon = $2.to_i day = $1.to_i hour = $4.to_i min = $5.to_i sec = $6.to_i # puts "Debug: #{year} #{mon} #{day} #{hour}:#{min}:#{sec}" usec = 0 usec = $7.to_f * 1000000 if $7 if $8 zone = $8 year, mon, day, hour, min, sec = apply_offset(year, mon, day, hour, min, sec, zone_offset(zone)) Time.utc(year, mon, day, hour, min, sec, usec) else Time.local(year, mon, day, hour, min, sec, usec) end else raise ArgumentError.new("invalid date: #{date.inspect}") end end end