OSDN Git Service

fixed multi-window using.
[feedblog/feedgenerator.git] / feedgenerator.rb
1 #!/usr/local/bin/ruby
2 # -*- coding: utf-8 -*-
3 #
4 #= Atom Feed 1.0を管理するWEBアプリケーション
5 #
6 #Autohr::    Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
7 #Version::   2.0.0.0
8 #Copyright:: Copyright 2009 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
9 #License::   GPLv3
10
11 require "cgi"
12 require "cgi/session"
13 require "erb"
14 require "rexml/document"
15 require "pstore"
16 require 'time'
17 require "date"
18 require "fileutils"
19
20 # ログインID
21 LOGINID = "login"
22 # ログインパスワード
23 PASSWORD = "password"
24 # インターフェースのテーブルの幅
25 TABLEWIDTH = 800
26 # XMLファイル格納先までの相対パス
27 XMLPATH = "./../lunardial/xml/"
28 # loglist.xmlファイルの定義
29 LISTXMLPATH = "#{XMLPATH}loglist.xml"
30 # FeedBlog上の表示ページからログ格納ディレクトリまでのパス
31 FEEDXMLDIR = "./xml/"
32 # デバッガモード
33 DEBUG = false
34 # ファイルマネージャー機能を使用するならtrue
35 USEFILEMANAGER = true
36 # ファイルマネージャー機能スクリプト(filemanager.rb)のパス
37 FILEMANAGER = "./filemanager.rb"
38 # XMLに書き込む際、改行部分を<br>のまま保持するか、改行記号に直すか
39 REPLACEBRTAG = false
40
41 # バージョン情報を示す文字列です
42 APPVERSION = "- FeedGenerator for Ruby version 2.0.0.0 -<br>Copyright(c) 2009 Kureha.H (<a href=\"http://lunardial.sakura.ne.jp/\" target=\"_blank\">http://lunardial.sakura.ne.jp/</a>) & Yui Naruse (<a href=\"http://airemix.com/\" target=\"_blank\">http://airemix.com/</a>)"
43 # タイトル領域に表示される文字列です
44 APPTITLE = "FeedGenerator for Ruby version 2.0.0.0"
45
46 # = Objectクラス
47 #
48 # 基本クラスのオーバーライドを行います
49 class Object
50   # myopenメソッド
51   #
52   # ruby-1.9.x以降ではファイルを開いた際、エンコードの指定を行わないとエラーの原因になります。
53   # ただしruby-1.8.6以前はエンコードの指定に対応していないため、独自メソッドを定義してファイルの入出力を行います。
54   #
55   # _arg[0]_ :: 入出力を行うファイルのパス
56   # _arg[1]_ :: モードの指定。例 : w:utf-8(書き込みモード・UTF-8エンコードでファイルを開く)
57   def myopen(*arg)
58     mode = arg[1]
59     arg[1] = mode[/[^:]+/] if mode && RUBY_VERSION < "1.8.7" && mode.include?(':')
60     open(*arg) do |f|
61       f.flock(/\A\w+r/ =~ mode ? File::LOCK_SH : File::LOCK_EX)
62       return yield(f)
63     end
64   end
65 end
66
67 class NilClass
68   def blank?
69     nil?
70   end
71 end
72
73 class Array
74   def blank?
75     empty?
76   end
77 end
78
79 class String
80   def blank?
81     empty?
82   end
83 end
84
85 # = Feed/Entryのスーパークラス
86
87 # 入出力用のメソッドなど、共通する機能を提供します
88 class AbstractEntry
89   # 初期化メソッドです
90   #
91   # _hash_ :: 値を格納したhash配列。superfeed.new(CGI::Session.new(new CGI).params)のようにして使う。
92   def initialize(hash)
93     # 内部データ保持用のハッシュ配列です
94     @attr = {}
95     
96     # 引数を格納します
97     @paramlist.each do |key|
98       val = hash[key.to_sym] || hash[key.to_s]
99       if val
100         # val.strip!
101         val.gsub!(/\r\n|\r/, "\n")
102         @attr[key.to_sym] = CGI.escapeHTML(val)
103       else
104         # 空の場合の対策
105         @attr[key.to_sym] = ""
106       end
107     end
108     
109     # 可視・不可視を示すハッシュキーを格納する配列です
110     @display = {}
111     
112     # ハッシュキーの日本語説明を格納する配列です
113     @name = []
114   end
115   
116   # Accessor
117   attr_reader :attr, :paramlist, :name, :display
118   
119   # 内部の@attrハッシュにアクセスする手段を提供するメソッドです
120   #
121   # AbstractEntry.attr[:title]にアクセスしたい場合はAbstractEntry.titleで可能になります。
122   # AbstractEntry.send("title")という方法でもアクセス可能です。
123   def method_missing(methname, *args)
124     methname = methname.to_s
125     
126     if methname[-1] == ?=
127       # setter
128       raise ArgumentError, "wrong number of arguments (#{args.length} for 1)" unless args.length == 1
129       methname.chop!
130       methname = @paramlist.find{|par|par == methname}
131       return @attr[methname.to_sym] = args[0] if methname
132     else
133       # getter
134       raise ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty?
135       return @attr[methname.to_sym] if @attr.key?(methname.to_sym)
136     end
137     # attr上にキーがない値を入れようとした場合はNoMethodError
138     raise NoMethodError
139   end
140   
141   def [](key)
142     @attr[key.to_sym]
143   end
144   
145   def []=(key, value)
146     @attr[key.to_sym] = value
147   end
148 end
149
150 # = Feedクラス
151 #
152 # Feedの基礎情報を保有するクラスです
153 class Feed < AbstractEntry
154   # 初期化メソッドです
155   #
156   # _hash_ :: 値を格納したhash配列。feed.new(CGI::Session.new(new CGI).params)のようにして使います。
157   def initialize(hash)
158     # 内部データ保持用のハッシュ配列です
159     @attr = {}
160     
161     # 内部データの項目名を格納する配列です
162     @paramlist = ["feedattr", "title", "subtitle", "self", "url", "updated", "feedid", "rights", "aname", "amail", "others"]
163     
164     # AbstractEntryのinitializeメソッドを呼び出し、値を@attr配列に格納します
165     super(hash)
166     
167     # 可視・不可視を示すハッシュキーを格納する配列です
168     @display = {"feedattr" => "none", "title" => "", "subtitle" => "", 
169       "self" => "", "url" => "", 
170       "updated" => "none", "feedid" => "", 
171       "rights" => "", "aname" => "", 
172       "amail" => "", "others" => "none"}
173     
174     # デバッグモードの場合、全ての入力要素を表示します
175     if DEBUG == true
176       @display.each do |key, val|
177         @display[key] = ""
178       end
179     end
180     
181     # ハッシュキーの日本語説明を格納する配列です
182     @name = {"feedattr" => "feedの保持している属性", "title" => "ウェブページのタイトル", "subtitle" => "ウェブページの簡単な説明", 
183       "self" => "このXMLファイルのURL", "url" => "ウェブページのURL", 
184       "updated" => "XMLファイルの最終更新日", "feedid" => "あなたのページ独自のID", 
185       "rights" => "ページの著作権表記", "aname" => "ページの製作者", 
186       "amail" => "ページ製作者のメールアドレス", "others" => "その他の要素"}
187     
188   end
189   
190   # Atom XMLファイルを読み込んで解析し、等価なFeedオブジェクトを返却するメソッドです
191   #
192   # _path_ :: Atom XMLファイルのパス
193   def Feed.readxml(path)
194     
195     # ファイルを読み込みます
196     doc = REXML::Document.new(myopen(path, "r:utf-8"){|f|f.read})
197     xml = {}
198     others = []
199     
200     # Feedの属性値を取得します
201     xmlattr = []
202     doc.elements["feed"].attributes.each_attribute do |attr|
203       xmlattr.push("#{attr.to_string} ")
204     end
205     xml[:feedattr] = xmlattr.join("\n").gsub(/"/, "'")
206     
207     # XML解析部分です。各element毎に判定を行います
208     doc.elements.each("feed") do |elm|
209       elm.elements.each { |child|
210         begin
211           case child.name
212             when "id"
213             xml[:feedid] = child.text
214             when "title", "subtitle", "updated", "rights"
215             xml[child.name.to_sym] = child.text
216             when "author"
217             child.elements.each do |gchild|
218               case gchild.name
219                 when "name"
220                 xml[:aname] = gchild.text
221                 when "email"
222                 xml[:amail] = gchild.text
223               end
224             end
225             when "link"
226             child.attributes.each do |k, v|
227               if k == "rel"
228                 if v == "self"
229                   xml[:self] = child.attributes["href"]
230                 elsif v == "alternate"
231                   xml[:url] = child.attributes["href"]
232                 end
233               end
234             end
235             when "entry"
236             # Entry要素は無視します
237           else
238             # 上記判定以外の全要素は配列に格納します
239             others.push(child.to_s)
240           end
241         rescue NoMethodError
242         end
243         
244       }
245     end
246     
247     # Others要素を結合して代入します
248     xml[:others] = others.join("\n")
249     feed = Feed.new(xml)
250     return feed
251   end
252   
253   # 内部に保持している情報を、Atom Feed1.0形式の文字列に出力するメソッドです
254   def to_s
255     buf = []
256     
257     buf.push("<feed #{@attr[:feedattr]}>")
258     buf.push("<title type=\"text\">#{@attr[:title]}</title>")
259     buf.push("<subtitle type=\"text\">#{@attr[:subtitle]}</subtitle>")
260     buf.push("<link rel=\"self\" type=\"application/atom+xml\" href=\"#{@attr[:self]}\" />")
261     buf.push("<link rel=\"alternate\" type=\"text/html\" href=\"#{@attr[:url]}\" />")
262     buf.push("<updated>#{Time.now.iso8601}</updated>")
263     buf.push("<id>#{@attr[:feedid]}</id>")
264     buf.push("<rights type=\"text\">#{@attr[:rights]}</rights>")
265     buf.push("<author>")
266     buf.push("\t<name>#{@attr[:aname]}</name>")
267     buf.push("\t<email>#{@attr[:amail]}</email>")
268     buf.push("</author>")
269     buf.push("#{CGI.unescapeHTML(@attr[:others])}") if @attr[:others] != ""
270     
271     return buf.join("\n")
272   end
273 end
274
275 # = Entryクラス
276 #
277 # Entryの基礎情報を保有するクラスです
278 class Entry < AbstractEntry
279   # 初期化メソッドです
280   #
281   # _hash_ :: 値を格納したhash配列。entry.new(CGI::Session.new(new CGI).params)のようにして使います。
282   def initialize(hash)
283     # 内部データ保持用のハッシュ配列です
284     @attr = {}
285     
286     # 内部データの項目名を格納する配列です
287     @paramlist = ["entryid", "title", "summary", "published", "updated", "url", "content", "others"]
288     
289     # AbstractEntryのinitializeメソッドを呼び出し、値を@attr配列に格納します
290     super(hash)
291     
292     # 可視・不可視を示すハッシュキーを格納する配列です
293     @display = {"entryid" => "none", "title" => "",
294       "summary" => "", "published" => "none",
295       "updated" => "none", "url" => "none",
296       "content" => "", "others"=>"none"}
297     
298     # デバッグモードの場合、全ての入力要素を表示します
299     if DEBUG == true
300       @display.each do |key, val|
301         @display[key] = ""
302       end
303     end
304     
305     # ハッシュキーの日本語説明を格納する配列です
306     @name = {"entryid" => "記事固有のID", "title" => "記事のタイトル",
307       "summary" => "記事の簡単な説明", "published" => "記事の出版時刻",
308       "updated" => "記事の更新時刻", "url" => "記事へのURLアドレス",
309       "content" => "記事の本文", "others"=>"その他の項目"}
310   end
311   
312   # Atom XMLファイルを読み込んで解析し、等価なEntryオブジェクト配列を返却するメソッドです
313   #
314   # _path_ :: Atom XMLファイルのパス
315   def Entry.readxml(path)
316     
317     # ファイルを読み込みます
318     doc = REXML::Document.new(myopen(path, "r:utf-8"){|f|f.read})
319     entrylist = []
320     xml = {}
321     
322     # XML解析部分です。各element毎に判定を行います
323     doc.elements.each("feed/entry") do |elm|
324       xml = {}
325       others = []
326       elm.elements.each do |child|
327         begin
328           case child.name
329             when "id"
330             xml[:entryid] = child.text
331             when "link"
332             xml[:url] = child.attributes["href"]
333             when "title", "summary", "summary", "published", "updated", "content"
334             xml[child.name.to_sym] = child.text
335           else
336             # 上記判定以外の全要素は配列に格納します
337             others.push(child.to_s)
338           end
339         rescue NoMethodError
340         end
341       end
342       # Others要素を結合して代入します
343       xml[:others] = others.join("\n")
344       entrylist.push(Entry.new(xml))
345     end
346     
347     return entrylist
348   end
349   
350   # データソースから読み取ったHTMLを、エディタで編集可能な形式に変換するメソッドです
351   def content_for_generator
352     str = @attr[:content].dup
353     # str.strip!
354     str.gsub!(/(&lt;\/(?:p|h\d|div)(?:&gt;|>))\n/i, '\1')
355     str.gsub!(/\n/, '&lt;br&gt;') if REPLACEBRTAG
356     str.gsub!(/(&lt;(?:(?!&gt;).)*?)#{Regexp.escape(FEEDXMLDIR)}/) { "#$1#{XMLPATH}" }
357     str
358   end
359   
360   # エディタで編集されたCONTENT要素を、データソースに書き込める形式に変換するメソッドです
361   def content_for_blog
362     str = @attr[:content].dup
363     str = CGI.unescapeHTML(str)
364     # str.strip!
365     str.gsub!(/(\r\n|\n)/, "")
366     str.gsub!(/<br>/i, "\n") if REPLACEBRTAG
367     str.gsub!(/(<br>|<\/p>|<\/h\d>|<\/div>)(?=[^\n])/i) { "#$1\n" } unless REPLACEBRTAG
368     str.gsub!(/(<[^>]*?)#{Regexp.escape(XMLPATH)}/) { "#$1#{FEEDXMLDIR}" }
369     CGI.escapeHTML(str)
370   end
371   
372   # 確認画面で表示されるCONTENT要素を生成するメソッドです
373   def content_for_view
374     str = @attr[:content].dup
375     str = CGI.unescapeHTML(str)
376     # str.strip!
377     str.gsub!(/<br>/i, "\n") if REPLACEBRTAG
378     str.gsub!(/(<[^>]*?)#{Regexp.escape(FEEDXMLDIR)}/) { "#$1#{XMLPATH}" }
379     str
380   end
381   
382   # 内部に保持している情報を、Atom Feed1.0形式の文字列に出力するメソッドです
383   def to_s
384     buf = []
385     buf.push("<entry>")
386     buf.push("<id>#{@attr[:entryid]}</id>")
387     buf.push("<title>#{@attr[:title]}</title>")
388     buf.push("<summary>#{@attr[:summary]}</summary>")
389     buf.push("<published>#{@attr[:published]}</published>")
390     buf.push("<updated>#{@attr[:updated]}</updated>")
391     buf.push("<link href=\"#{@attr[:url]}\" />")
392     buf.push("<content type=\"html\">#{@attr[:content]}</content>")
393     buf.push("#{CGI.unescapeHTML(@attr[:others])}") if @attr[:others] != ""
394     buf.push("</entry>")
395     
396     return buf.join("\n")
397   end
398 end
399
400 # = FeedWriterクラス
401 #
402 # Atom Feed 1.0 XMLファイルに書き込みを行うクラスです
403 class FeedWriter
404   # 初期化メソッドです
405   #
406   # _path_ :: Atom Feed 1.0 XMLファイルの書き込みパス
407   def initialize(path)
408     @path = path
409   end
410   
411   # XMLファイル出力用メソッドです
412   #
413   # _feed_ :: Feedオブジェクト
414   # _entry_ :: Entryオブジェクトの配列
415   def to_xml(feed, entrylist)
416     buf = []
417     buf.push("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
418     buf.push("#{feed.to_s}\n")
419     entrylist.each { |entry|
420       buf.push("#{entry.to_s}\n")
421     }
422     buf.push("</feed>")
423     
424     myopen(@path, "w") do |f|
425       f.print buf.join("\n")
426     end
427   end
428 end
429
430 # = HtmlWriterクラス
431
432 # テンプレートファイル(*.erb)を読み込み、管理するクラスです
433 class HtmlWriter
434   # 初期化メソッドです
435   #
436   # _template_ :: テンプレートファイル(*.erb)のパス
437   # _binding_ :: binding変数
438   def initialize(template, binding)
439     @erb = ERB.new(myopen(template, "r:utf-8") {|f| f.read}, nil, "-")
440     @binding = binding
441   end
442   
443   # テンプレートファイルの文字列を返却するメソッドです
444   def to_code
445     @erb.result(@binding)
446   end
447 end
448
449 # = LogListクラス
450 #
451 # loglist.xmlにかかわる入出力を行います
452 class LogList
453   # 初期化メソッドです
454   #
455   # _display_ :: 画面表示用文字の配列
456   # _path_ :: XMLファイルパスの配列
457   # _logpath_ :: loglist.xmlのパス
458   def initialize(display, path, logpath)
459     @display = display
460     @path = path
461     @logpath = logpath
462   end
463   
464   attr_accessor :display, :path, :logpath
465   
466   # loglist.xmlファイルを読み込んで解析し、LogListオブジェクトを返却するメソッドです
467   #
468   # _logpath_ :: loglist.xmlへのパス
469   def LogList.readxml(logpath)
470     
471     # ファイルを読み込みます
472     lines = []
473     myopen(logpath, "r:utf-8") { |f|
474       lines = f.readlines
475     }
476     
477     doc = REXML::Document.new(lines.join("\n"))
478     @display = []
479     @path = []
480     
481     doc.elements.each("list/file") { |elm|
482       elm.elements.each {|child|
483         case child.name
484           when "display"
485           @display.push(child.text)
486           when "path"
487           @path.push(child.text)
488         else 
489           # With no action
490         end
491       }
492     }
493     
494     return LogList.new(@display, @path, logpath)
495   end
496   
497   # loglist.xmlにオブジェクトの内容を反映するメソッドです
498   def to_xml
499     buf = []
500     buf.push("<list>")
501     path.each_with_index do |path, i|
502       buf.push("<file>")
503       buf.push("<display>#{@display[i]}</display>")
504       buf.push("<path>#{@path[i]}</path>")
505       buf.push("</file>")
506     end
507     buf.push("</list>")
508     
509     myopen(@logpath, "w") do |f|
510       f.print buf.join("\n")
511     end
512   end
513   
514 end
515
516 # = FileUploaderクラス
517 #
518 # 画像の管理を行うクラスです
519 class FileUploader
520   # 初期化メソッドです
521   def initialize
522   end
523   
524   # ファイルの一覧を取得し、ファイル名称を格納した配列を返却します
525   def filelist
526     arr = Dir::entries(XMLPATH).sort
527     arr.delete(".")
528     arr.delete("..")
529     
530     return arr
531   end
532   
533   # ファイルの消去を実行します
534   #
535   # _name_ :: 消去するファイル名
536   def delete(name)
537     File.delete(XMLPATH + name.match(/[^\/]*?$/).to_a[0])
538   end
539   
540   # ファイルのアップロードを実行します
541   #
542   # _name_ :: アップロードファイルの名称
543   # _img_ :: アップロードファイル
544   def upload(name, img)
545     open(XMLPATH + File.basename(name), "w") do |f|
546       f.binmode
547       f.write img
548     end
549   end
550 end
551
552 # = Controllerクラス
553 #
554 # コントローラ部分に相当する処理を受け持つクラスです
555 class Controller
556   def Controller.NormalForm(cgi, session, params, db)
557     db.transaction do
558       # modeとactionの相関図は以下のようになります。
559       # [mode] + [action]
560       #        + [action]
561       #        + [action]
562       #        + ... and more
563       case params["mode"]
564         # ログ選択画面
565         when "logselect"
566         session["filepath"] = params["logpath"]
567         session["loglist"] = LogList.readxml(LISTXMLPATH)
568         # 初期状態で選択されるログは「loglist.xml」の最上に位置するログになります
569         session["filepath"] = session["loglist"].path[0] if session["filepath"] == nil
570         session["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
571         session["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
572         
573         # 新規記事追加部分
574         when "newentry"
575         case params["action"]
576           # 確認画面
577           when "confirm"
578           session["newentry"] = Entry.new(params)
579           session["newentry"].content = session["newentry"].content_for_blog
580           # 記事の追記を実際にファイルに反映
581           when "exec"
582           session["newentry"] = Entry.new(params)
583           session["entry"].unshift session["newentry"]
584           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
585           feedwriter.to_xml(session["feed"], session["entry"])
586           # 確認画面から戻った場合
587           when "back"
588           session["newentry"] = Entry.new(params)
589         else 
590           # New Diary - Default
591           session["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
592           session["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
593         end
594         
595         # 記事編集部分
596         when "editentry"
597         case params["action"]
598           # 編集画面
599           when "edit"
600           session["editid"] = cgi["editid"].to_s
601           # 確認画面
602           when "confirm"
603           session["editid"] = cgi["editid"].to_s
604           session["editentry"] = Entry.new(params)
605           session["editentry"].content = session["editentry"].content_for_blog
606           # 記事の変更を実際にファイルに反映
607           when "exec"
608           session["editentry"] = Entry.new(params)
609           session["entry"].each_with_index { |e, i|
610             session["entry"][i] = session["editentry"] if e.entryid == cgi["editid"].to_s
611           }
612           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
613           feedwriter.to_xml(session["feed"], session["entry"])
614           # 確認画面から戻った場合
615           when "back"
616           session["editid"] = cgi["editid"].to_s
617           session["editentry"] = Entry.new(params)
618         else
619           # Edit Diary - Default
620           session["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
621           session["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
622         end
623         
624         # 記事削除部分
625         when "delentry"
626         case params["action"]
627           # 確認画面
628           when "confirm"
629           session["delid"] = cgi["delid"].to_s
630           # 記事の削除を実際にファイルに反映
631           when "exec"
632           delindex = nil
633           session["entry"].each_with_index { |e, i|
634             delindex = i if e.entryid == cgi["delid"].to_s
635           }
636           session["entry"].delete_at(delindex) unless delindex == nil
637           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
638           feedwriter.to_xml(session["feed"], session["entry"])
639         else
640           # Delete Diary - Default
641           session["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
642           session["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
643         end
644         
645         # Feed情報変更部分
646         when "editfeed"
647         case params["action"]
648           # 確認画面
649           when "confirm"
650           session["feed"] = Feed.new(params)
651           # 実際にFeed情報の変更をファイルに反映
652           when "exec"
653           session["feed"] = Feed.new(params)
654           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
655           feedwriter.to_xml(session["feed"], session["entry"])
656           # 確認画面から戻った場合
657           when "back"
658           session["feed"] = Feed.new(params)
659         else
660           
661         end
662         
663         # ログ編集モード
664         when "log"
665         case params["action"]
666           # ログファイルの編集を実際にファイルに反映
667           when "addexec"
668           # 入力されたログが既に存在するかを確認
669           session["loglist"].path.each do |val|
670             if val == cgi["logpath"]
671               # 重複していた場合エラーメッセージを表示し、処理を行わない
672               params["action"] = ""
673               error = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
674               break;
675             end
676           end
677           
678           session["logpath"] = cgi["logpath"]
679           session["logdisplay"] = cgi["logdisplay"]
680           
681           # 入力された内容を保持する配列に追加
682           session["loglist"].path[1, 0] = session["logpath"]
683           session["loglist"].display[1, 0] = session["logdisplay"]
684           session["loglist"].to_xml
685           
686           # 既存のdiary.xmlを指定された名称でコピーして保存
687           FileUtils.copy_file(XMLPATH + session["loglist"].path[0].match(/[^\/]*?$/).to_a[0], XMLPATH + session["logpath"].match(/[^\/]*?$/).to_a[0], true)
688           
689           # 保持している情報を更新する
690           session["loglist"] = LogList.readxml(LISTXMLPATH)
691           session["entry"] = []
692           
693           # 新たなdiary.xmlを生成。この際保持する情報は同一のFeedオブジェクト
694           writer = FeedWriter.new(XMLPATH + session["loglist"].path[0].match(/[^\/]*?$/).to_a[0])
695           writer.to_xml(session["feed"], [])
696           
697           # 確認画面
698           when "addconfirm"
699           # 入力されたログが既に存在するかを確認
700           session["loglist"].path.each do |val|
701             if val == cgi["logpath"]
702               # 重複していた場合エラーメッセージを表示し、処理を行わない
703               params["action"] = ""
704               error = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
705               break;
706             end
707           end
708           
709           session["logpath"] = cgi["logpath"]
710           session["logdisplay"] = cgi["logdisplay"]
711           
712           if session["logpath"].blank? || session["logdisplay"].blank?
713             params["action"] = ""
714           end
715           when "back"
716           # 削除確認画面
717           when "delconfirm"
718           session["logdelindex"] = params["logdelindex"].to_i
719           
720           if session["logdelindex"] == 0
721             params["action"] = ""
722           end
723           # 削除処理
724           when "delexec"
725           session["logdelindex"] = params["logdelindex"].to_i
726           if session["logdelindex"] == 0
727             params["action"] = ""
728           else
729             # 記事ファイルを削除します
730             File.delete(XMLPATH + session["loglist"].path[session["logdelindex"]].match(/[^\/]*?$/).to_s)
731             # ログリストから削除します
732             session["loglist"].path.delete_at(session["logdelindex"])
733             session["loglist"].display.delete_at(session["logdelindex"])
734             session["loglist"].to_xml
735             # 保持している情報を更新する
736             session["loglist"] = LogList.readxml(LISTXMLPATH)
737             session["entry"] = []
738           end
739           # 編集画面
740           when "edit"
741           session["logeditindex"] = params["logdelindex"].to_i
742           
743           session["logpath"] = session["loglist"].path[session["logeditindex"]]
744           session["logdisplay"] = session["loglist"].display[session["logeditindex"]]
745           
746           if session["logeditindex"] == 0
747             params["action"] = ""
748           end
749           # 編集確認画面
750           when "editconfirm"
751           session["logeditindex"] = params["logeditindex"].to_i
752           checkflag = true
753           session["loglist"].path.each_with_index do |val, i|
754             if session["logeditindex"] != i
755               if params["logpath"].to_s == session["loglist"].path[i].to_s
756                 checkflag = false
757               end
758             end
759           end
760           
761           if checkflag == false
762             params["action"] = "edit"
763             session["error"] = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
764           else
765             session["loginsertindex"] = params["loginsertindex"].to_i
766             
767             session["logpath"] = params["logpath"].to_s
768             session["logdisplay"] = params["logdisplay"].to_s
769           end
770           # 編集実行
771           when "editexec"
772           session["logeditindex"] = params["logeditindex"].to_i
773           checkflag = true
774           session["loglist"].path.each_with_index do |val, i|
775             if session["logeditindex"] != i
776               if params["logpath"].to_s == session["loglist"].path[i].to_s
777                 checkflag = false
778               end
779             end
780           end
781           
782           if checkflag == false
783             params["action"] = "edit"
784             session["error"] = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
785             return
786           else
787             session["loginsertindex"] = params["loginsertindex"].to_i
788             
789             session["logpath"] = params["logpath"].to_s
790             session["logdisplay"] = params["logdisplay"].to_s
791           end
792           
793           # ファイルを移動します
794           if XMLPATH + session["loglist"].path[session["logeditindex"]].match(/[^\/]*?$/).to_s != XMLPATH + session["logpath"].match(/[^\/]*?$/).to_s
795             FileUtils.move(XMLPATH + session["loglist"].path[session["logeditindex"]].match(/[^\/]*?$/).to_s, XMLPATH + session["logpath"].match(/[^\/]*?$/).to_s)
796           end
797           # ログリストを更新します
798           session["loglist"].path.delete_at(session["logeditindex"])
799           session["loglist"].display.delete_at(session["logeditindex"])
800           session["loglist"].path.insert(session["loginsertindex"] + 1, session["logpath"])
801           session["loglist"].display.insert(session["loginsertindex"] + 1, session["logdisplay"])
802           session["loglist"].to_xml
803           # 初期表示画面
804         else
805           # 初期表示の際に内部保持データをリフレッシュ
806           session["loglist"] = LogList.readxml(LISTXMLPATH)
807           
808           # 現在編集中のデータを強制的に最上のファイルパスに変更
809           session["filepath"] = session["loglist"].path[0]
810           
811           # 前月の時刻を作成します
812           prevmonth = (DateTime.now << 1)
813           
814           # 前月の時刻を元に、ディフォルト書式を生成します
815           session["logpath"] = FEEDXMLDIR + prevmonth.strftime("%Y%m") + ".xml"
816           session["logdisplay"] = prevmonth.strftime("%Y年%m月").gsub("年0", "年")
817         end
818         
819         # インポートモードの場合の処理
820         when "import"
821         session["loglist"] = LogList.readxml(LISTXMLPATH)
822         
823         # リセットモードの場合の処理
824         when "reset"
825         case params["action"]
826           # リセット実行時の処理
827           when "exec"
828           file = FileUploader.new
829           file.filelist().each { |fname|
830             file.delete(fname) if File.ftype(XMLPATH + fname) == "file"
831           }
832           
833           # 全ファイルの初期化を実行する
834           # loglist.xmlの初期化
835           loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}diary.xml"], LISTXMLPATH)
836           loglist.to_xml
837           writer = FeedWriter.new(XMLPATH + loglist.path[0].match(/[^\/]*?$/).to_a[0])
838           writer.to_xml(Feed.new({}), [])
839           
840           session["loglist"] = LogList.readxml(LISTXMLPATH)
841           
842           # diary.xmlの初期化
843           writer = FeedWriter.new(XMLPATH + session["loglist"].path[0].match(/[^\/]*?$/).to_a[0])
844           writer.to_xml(Feed.new({}), [])
845           
846           # 初期の編集ファイルはloglist.xml上の最も上のファイル
847           session["filepath"] = session["loglist"].path[0]
848           session["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
849           session["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
850           
851           # 画面の遷移先をトップページにする
852           params["mode"] = ""
853           params["action"] = ""
854           # パラメタ無しの処理
855         else
856           # ファイル一覧を取得する
857           filelist = FileUploader.new.filelist()
858           
859           # タイプがファイルのみリストとして取得する
860           session["filelist"] = []
861           filelist.each { |fname| session["filelist"] << fname if File.ftype(XMLPATH + fname) == "file"}
862         end
863         
864         # ログイン時の画面
865       else
866         # loglist.xmlが存在するかチェック
867         if File.exist?(LISTXMLPATH) == false
868           # なかった場合はloglist.xmlを自動生成
869           loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}diary.xml"], LISTXMLPATH)
870           loglist.to_xml
871           writer = FeedWriter.new(XMLPATH + loglist.path[0].match(/[^\/]*?$/).to_a[0])
872           writer.to_xml(Feed.new({}), [])
873         end
874         
875         session["loglist"] = LogList.readxml(LISTXMLPATH)
876         
877         # diary.xmlが存在するかチェック
878         if File.exist?(XMLPATH + session["loglist"].path[0].match(/[^\/]*?$/).to_a[0]) == false
879           # なかった場合はdiary.xmlを自動生成
880           writer = FeedWriter.new(XMLPATH + session["loglist"].path[0].match(/[^\/]*?$/).to_a[0])
881           writer.to_xml(Feed.new({}), [])
882         end
883         
884         # 初期の編集ファイルはloglist.xml上の最も上のファイル
885         session["filepath"] = session["loglist"].path[0] if session["filepath"] == nil
886         session["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
887         session["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
888       end
889     end
890   end
891   
892   # マルチパートフォームの場合の処理です
893   def Controller.MultiForm(cgi, session, params, db)
894     db.transaction do
895       case params["mode"]
896         # 特定位置に挿入する場合の処理
897         when "insert"
898         case params["action"]
899           when "exec"
900           # ファイルを実際にアップロードします
901           file = FileUploader.new
902           file.upload(session["logpath"], session["importxml"])
903           
904           # loglist.xmlを更新します
905           session["loglist"].path[session["loginsertindex"], 0] = session["logpath"]
906           session["loglist"].display[session["loginsertindex"], 0] = session["logdisplay"]
907           session["loglist"].to_xml
908           
909           # 情報を更新します
910           session["loglist"] = LogList.readxml(LISTXMLPATH)
911           session["filepath"] = session["loglist"].path[0] if session["filepath"] == nil
912           session["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
913           session["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
914           
915           when "confirm"
916           # 入力されたログファイルパスが既に存在するかを確認
917           checkflag = true
918           session["logpath"] = Controller.get_mpart_value(cgi["logpath"])
919           session["loglist"].path.each do |val|
920             if val == session["logpath"]
921               # 重複していた場合エラーメッセージを表示し、処理を行わない
922               session["logpath"] = ""
923               params["action"] = ""
924               session["error"] = "<br><span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
925               checkflag = false
926               break;
927             end
928           end
929           
930           if checkflag == true
931             session["loginsertindex"] = cgi["loginsertindex"].read.to_i
932             session["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
933             session["importxml"] = cgi["updata"].read
934             
935             # XMLの整合性をチェックします
936             begin
937               REXML::Document.new(Controller.fix_updata_enc(session["importxml"].to_s))
938             rescue => exception
939               session["error"] = "<br><span style='color: #ff0000'>不正な整形のXMLです!ファイルを見直してください。</span><br>"
940               session["error"] << "<div class='divstyle' style='width: 560px; color: #ff0000; text-align: left;'>"
941               session["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
942               session["error"] << "</div>"
943               params["action"] = ""
944             end
945           end
946           
947           # 0位置への挿入防止
948           if session["loginsertindex"] == 0
949             params["action"] = ""
950             session["error"] = "<br><span style='color: #ff0000'>ラジオボックスでログの挿入位置を選択してください!</span><br>"
951           end
952           
953           if session["logpath"] == FEEDXMLDIR || session["logdisplay"].blank?
954             params["action"] = ""
955             session["error"] = "<br><span style='color: #ff0000'>インポートファイル、及び、ログの表示名は空欄にできません。</span><br>"
956           end
957         else
958           # loglist.xmlをロードします
959           session["loglist"] = LogList.readxml(LISTXMLPATH)
960         end
961         
962         # diary.xmlを入れ替える処理
963         when "replace"
964         case params["action"]
965           when "exec"
966           # diary.xmlを移動します
967           FileUtils.move(XMLPATH + "diary.xml", XMLPATH + session["logpath"].match(/[^\/]*?$/).to_s)
968           # ファイルをアップロードします
969           file = FileUploader.new
970           file.upload("diary.xml", session["importxml"])
971           
972           # loglist.xmlを更新します
973           session["loglist"].path[1, 0] = session["logpath"]
974           session["loglist"].display[1, 0] = session["logdisplay"]
975           session["loglist"].to_xml
976           
977           # 情報を更新します
978           session["loglist"] = LogList.readxml(LISTXMLPATH)
979           session["filepath"] = session["loglist"].path[0] if session["filepath"] == nil
980           session["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
981           session["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
982           
983           when "confirm"
984           # 入力されたログファイルパスが既に存在するかを確認
985           checkflag = true
986           session["logpath"] = Controller.get_mpart_value(cgi["logpath"])
987           session["loglist"].path.each do |val|
988             if val == session["logpath"]
989               # 重複していた場合エラーメッセージを表示し、処理を行わない
990               params["action"] = ""
991               session["error"] = "<br><span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
992               checkflag = false
993               break;
994             end
995           end
996           
997           if checkflag == true
998             session["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
999             session["importxml"] = cgi["updata"].read
1000             
1001             # XMLの整合性をチェックします
1002             begin
1003               REXML::Document.new(Controller.fix_updata_enc(session["importxml"].to_s))
1004             rescue => exception
1005               session["error"] = "<br><span style='color: #ff0000'>不正な整形のXMLです!ファイルを見直してください。</span><br>"
1006               session["error"] << "<div class='divstyle' style='width: 560px; color: #ff0000; text-align: left;'>"
1007               session["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
1008               session["error"] << "</div>"
1009               params["action"] = ""
1010             end
1011           end
1012           
1013           if session["logpath"].blank? || session["logdisplay"].blank? || session["importxml"].blank?
1014             params["action"] = ""
1015             session["error"] = "<span style='color: #ff0000'>インポートファイル、及び、ログの表示名、ログのパスは空欄にできません。</span><br>"
1016           end
1017         else
1018           # loglist.xmlをロードします
1019           session["loglist"] = LogList.readxml(LISTXMLPATH)
1020         end
1021       else
1022         # loglist.xmlをロードします
1023         session["loglist"] = LogList.readxml(LISTXMLPATH)
1024       end
1025     end
1026   end
1027   
1028   # Formのenctypeがmultypartだった場合、入力された値をrubyバージョンに左右されずに読み取るメソッドです。
1029   def Controller.get_mpart_value(cgi_param)
1030     if RUBY_VERSION >= "1.9.0"
1031       cgi_param[0..cgi_param.length].to_s
1032     else
1033       cgi_param.read.to_s
1034     end
1035   end
1036   
1037   # アップロードされたXMLをバージョンに左右されずに読み取るために、ruby-1.9.1以上ではエンコーディングを強制指定します
1038   def Controller.fix_updata_enc(file_to_s)
1039     if RUBY_VERSION >= "1.9.0"
1040       file_to_s.force_encoding("UTF-8")
1041     else
1042       file_to_s
1043     end
1044   end
1045   
1046 end
1047
1048 def main
1049   # SESSION変数、パラメータなどを取得します
1050   cgi = CGI.new
1051   session = CGI::Session.new(cgi)
1052   params = Hash[*cgi.params.to_a.map{|k, v| [k, v[0].to_s]}.flatten]
1053   
1054   # コントローラー部分
1055   # セッション管理
1056   if session["login"] != "true"
1057     if (cgi["loginid"] == LOGINID && cgi["password"] == PASSWORD)
1058       session["login"] = "true"
1059       
1060       # ワークフォルダの中をクリーンアップします
1061       filelist = Dir::entries("./work")
1062       # 削除条件 : 最終変更日時から1日(60*60*24sec)かつ、ファイルタイプがファイルの場合
1063       filelist.each do |file|
1064         File.delete("./work/#{file}") if Time.now - File.ctime("./work/#{file}") > 86400 && File.ftype("./work/#{file}") == "file"
1065       end
1066     end
1067   end
1068   
1069   # ログアウト処理
1070   if params["mode"] == "logout"
1071     session["login"] = nil
1072     session["logini"] = nil
1073     session["password"] = nil
1074     session.delete
1075   end
1076   
1077   begin
1078     # メインコントローラー
1079     # セッションが有効な場合のも実行します
1080     if session["login"] == "true"
1081       # PStore破損チェック!
1082       begin
1083         db = PStore.new("./work/#{session.session_id}.dat")
1084       rescue
1085         # PStoreファイルを破棄する
1086         File.delete("./work/#{session.session_id}.dat")
1087         # PStoreが破損していた場合はセッション情報を破棄する
1088         session["login"] = nil
1089         session["logini"] = nil
1090         session["password"] = nil
1091         session.delete
1092       end
1093       
1094       # フォームによって挙動を変更します
1095       if cgi["mode"].respond_to?(:read) && cgi["action"].respond_to?(:read)
1096         session["error"] = ""
1097         params["mode"] = cgi["mode"].read
1098         params["action"] = cgi["action"].read
1099         Controller.MultiForm(cgi, session, params, db)
1100       else
1101         Controller.NormalForm(cgi, session, params, db)
1102       end
1103     end
1104     
1105     # メニューとして表示されるHTML文字列です
1106     if USEFILEMANAGER == true
1107       menu = "<div class=\"divstyle\" style=\"width: #{TABLEWIDTH}px;\"><div class=\"divstyle\" align=\"center\" style=\"margin-bottom: 5px;\">現在編集中のファイル : #{session["filepath"]} [ <a href=\"#{cgi.script_name}?mode=selectlog\">他のファイルを選択</a> ]</div>[ <a href=\"#{cgi.script_name}\">トップページ</a> | 記事管理 ( <a href=\"#{cgi.script_name}?mode=newentry\">作成</a> | <a href=\"#{cgi.script_name}?mode=editentry\">編集</a> | <a href=\"#{cgi.script_name}?mode=delentry\">消去</a> )  | <a href=\"#{cgi.script_name}?mode=editfeed\">XML情報編集</a> | <a href=\"#{cgi.script_name}?mode=log\">ログ管理</a> | <a href=\"#{cgi.script_name}?mode=import\">インポート</a> | <a href=\"#{FILEMANAGER}\" target=\"_blank\">ファイル管理</a> | <a href=\"#{cgi.script_name}?mode=reset\">初期化</a> | <a href=\"#{cgi.script_name}?mode=logout\">ログアウト</a> ]</div>"
1108     else
1109       menu = "<div class=\"divstyle\" style=\"width: #{TABLEWIDTH}px;\"><div class=\"divstyle\" align=\"center\" style=\"margin-bottom: 5px;\">現在編集中のファイル : #{session["filepath"]} [ <a href=\"#{cgi.script_name}?mode=selectlog\">他のファイルを選択</a> ]</div>[ <a href=\"#{cgi.script_name}\">トップページ</a> | 記事管理 ( <a href=\"#{cgi.script_name}?mode=newentry\">作成</a> | <a href=\"#{cgi.script_name}?mode=editentry\">編集</a> | <a href=\"#{cgi.script_name}?mode=delentry\">消去</a> )  | <a href=\"#{cgi.script_name}?mode=editfeed\">XML情報編集</a> | <a href=\"#{cgi.script_name}?mode=log\">ログ管理</a> | <a href=\"#{cgi.script_name}?mode=import\">インポート</a> | <a href=\"#{cgi.script_name}?mode=reset\">初期化</a> | <a href=\"#{cgi.script_name}?mode=logout\">ログアウト</a> ]</div>"
1110     end
1111     
1112     # ビュー部分
1113     # modeとactionの相関図は以下のようになります。
1114     # [mode] + [action]
1115     #        + [action]
1116     #        + [action]
1117     #        + ... and more
1118     if session["login"] != "true"
1119       # セッションが存在しない場合は強制的にトップページに遷移
1120       htmlwriter = HtmlWriter.new("./erbtemp/login.html.erb", binding)
1121     else
1122       case params["mode"]
1123         when "selectlog"
1124         htmlwriter = HtmlWriter.new("./erbtemp/select.html.erb", binding)
1125         when "newentry"
1126         htmlwriter = HtmlWriter.new("./erbtemp/newentry.html.erb", binding)
1127         when "editentry"
1128         htmlwriter = HtmlWriter.new("./erbtemp/editentry.html.erb", binding)
1129         when "delentry"
1130         htmlwriter = HtmlWriter.new("./erbtemp/delentry.html.erb", binding)
1131         when "editfeed"
1132         htmlwriter = HtmlWriter.new("./erbtemp/editfeed.html.erb", binding)
1133         when "log"
1134         htmlwriter = HtmlWriter.new("./erbtemp/log.html.erb", binding)
1135         when "insert"
1136         htmlwriter = HtmlWriter.new("./erbtemp/insertfeed.html.erb", binding)
1137         when "replace"
1138         htmlwriter = HtmlWriter.new("./erbtemp/replacefeed.html.erb", binding)
1139         when "import"
1140         htmlwriter = HtmlWriter.new("./erbtemp/indeximport.html.erb", binding)
1141         when "reset"
1142         htmlwriter = HtmlWriter.new("./erbtemp/reset.html.erb", binding)
1143       else
1144         htmlwriter = HtmlWriter.new("./erbtemp/index.html.erb", binding)
1145       end
1146     end
1147   rescue => exception
1148     # エラーが発生した場合、それを画面に表示します
1149     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
1150   end
1151   
1152   # 実際にHTMLを出力します
1153   begin
1154     cgi.out{htmlwriter.to_code}
1155   rescue => exception
1156     # エラーが発生した場合、それを画面に表示します
1157     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
1158     cgi.out{htmlwriter.to_code}
1159   end
1160 end
1161
1162 begin
1163   main
1164 rescue => evar
1165   # エラーが発生した場合、それを画面に表示します
1166   detail = ("%s: %s (%s)\n" %
1167   [evar.backtrace[0], evar.message, evar.send('class')]) +
1168   evar.backtrace[1..-1].join("\n")
1169   puts "content-type: text/html\n\n<plaintext>\n" + detail
1170 end