OSDN Git Service

creating web-file-module.
[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         db["loglist"] = LogList.readxml(LISTXMLPATH)
568         # 初期状態で選択されるログは「loglist.xml」の最上に位置するログになります
569         session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
570         db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
571         db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
572         
573         # 新規記事追加部分
574         when "newentry"
575         case params["action"]
576           # 確認画面
577           when "confirm"
578           db["newentry"] = Entry.new(params)
579           db["newentry"].content = db["newentry"].content_for_blog
580           # 記事の追記を実際にファイルに反映
581           when "exec"
582           db["entry"].unshift db["newentry"]
583           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
584           feedwriter.to_xml(db["feed"], db["entry"])
585         else 
586           # New Diary - Default
587         end
588         
589         # 記事編集部分
590         when "editentry"
591         case params["action"]
592           # 編集画面
593           when "edit"
594           db["editindex"] = cgi["editindex"].to_i
595           # 確認画面
596           when "confirm"
597           db["editentry"] = Entry.new(params)
598           db["editentry"].content = db["editentry"].content_for_blog
599           # 記事の変更を実際にファイルに反映
600           when "exec"
601           db["entry"][db["editindex"]] = db["editentry"]
602           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
603           feedwriter.to_xml(db["feed"], db["entry"])
604         else
605         end
606         
607         # 記事削除部分
608         when "delentry"
609         case params["action"]
610           # 確認画面
611           when "confirm"
612           db["delindex"] = cgi["delindex"].to_i
613           # 記事の削除を実際にファイルに反映
614           when "exec"
615           db["entry"].delete_at(db["delindex"])
616           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
617           feedwriter.to_xml(db["feed"], db["entry"])
618         end
619         
620         # Feed情報変更部分
621         when "editfeed"
622         case params["action"]
623           # 確認画面
624           when "confirm"
625           db["feed"] = Feed.new(params)
626           # 実際にFeed情報の変更をファイルに反映
627           when "exec"
628           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
629           feedwriter.to_xml(db["feed"], db["entry"])
630         else
631           
632         end
633         
634         # ログ編集モード
635         when "log"
636         case params["action"]
637           # ログファイルの編集を実際にファイルに反映
638           when "addexec"
639           # 入力された内容を保持する配列に追加
640           db["loglist"].path[1, 0] = db["logpath"]
641           db["loglist"].display[1, 0] = db["logdisplay"]
642           db["loglist"].to_xml
643           # 既存のdiary.xmlを指定された名称でコピーして保存
644           FileUtils.copy_file(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0], XMLPATH + db["logpath"].match(/[^\/]*?$/).to_a[0], true)
645           # 保持している情報を更新する
646           db["loglist"] = LogList.readxml(LISTXMLPATH)
647           db["entry"] = []
648           # 新たなdiary.xmlを生成。この際保持する情報は同一のFeedオブジェクト
649           writer = FeedWriter.new(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0])
650           writer.to_xml(db["feed"], [])
651           # 確認画面
652           when "addconfirm"
653           # 入力されたログが既に存在するかを確認
654           db["loglist"].path.each do |val|
655             if val == cgi["logpath"]
656               # 重複していた場合エラーメッセージを表示し、処理を行わない
657               params["action"] = ""
658               error = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
659               break;
660             end
661           end
662           
663           db["logpath"] = cgi["logpath"]
664           db["logdisplay"] = cgi["logdisplay"]
665           
666           if db["logpath"].blank? || db["logdisplay"].blank?
667             params["action"] = ""
668           end
669           when "back"
670           # 削除確認画面
671           when "delconfirm"
672           db["logdelindex"] = params["logdelindex"].to_i
673           
674           if db["logdelindex"] == 0
675             params["action"] = ""
676           end
677           # 削除処理
678           when "delexec"
679           if db["logdelindex"] == 0
680             params["action"] = ""
681           else
682             # 記事ファイルを削除します
683             File.delete(XMLPATH + db["loglist"].path[db["logdelindex"]].match(/[^\/]*?$/).to_s)
684             # ログリストから削除します
685             db["loglist"].path.delete_at(db["logdelindex"])
686             db["loglist"].display.delete_at(db["logdelindex"])
687             db["loglist"].to_xml
688             # 保持している情報を更新する
689             db["loglist"] = LogList.readxml(LISTXMLPATH)
690             db["entry"] = []
691           end
692           # 編集画面
693           when "edit"
694           db["logeditindex"] = params["logdelindex"].to_i
695           
696           db["logpath"] = db["loglist"].path[db["logeditindex"]]
697           db["logdisplay"] = db["loglist"].display[db["logeditindex"]]
698           
699           if db["logeditindex"] == 0
700             params["action"] = ""
701           end
702           # 編集確認画面
703           when "editconfirm"
704           
705           checkflag = true
706           db["loglist"].path.each_with_index do |val, i|
707             if db["logeditindex"] != i
708               if params["logpath"].to_s == db["loglist"].path[i].to_s
709                 checkflag = false
710               end
711             end
712           end
713           
714           if checkflag == false
715             params["action"] = "edit"
716             db["error"] = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
717           else
718             db["loginsertindex"] = params["loginsertindex"].to_i
719             
720             db["logpath"] = params["logpath"].to_s
721             db["logdisplay"] = params["logdisplay"].to_s
722           end
723           # 編集実行
724           when "editexec"
725           # ファイルを移動します
726           if XMLPATH + db["loglist"].path[db["logeditindex"]].match(/[^\/]*?$/).to_s != XMLPATH + db["logpath"].match(/[^\/]*?$/).to_s
727             FileUtils.move(XMLPATH + db["loglist"].path[db["logeditindex"]].match(/[^\/]*?$/).to_s, XMLPATH + db["logpath"].match(/[^\/]*?$/).to_s)
728           end
729           # ログリストを更新します
730           db["loglist"].path.delete_at(db["logeditindex"])
731           db["loglist"].display.delete_at(db["logeditindex"])
732           db["loglist"].path.insert(db["loginsertindex"] + 1, db["logpath"])
733           db["loglist"].display.insert(db["loginsertindex"] + 1, db["logdisplay"])
734           db["loglist"].to_xml
735           # 初期表示画面
736         else
737           # 初期表示の際に内部保持データをリフレッシュ
738           db["loglist"] = LogList.readxml(LISTXMLPATH)
739           
740           # 現在編集中のデータを強制的に最上のファイルパスに変更
741           session["filepath"] = db["loglist"].path[0]
742           
743           # 前月の時刻を作成します
744           prevmonth = (DateTime.now << 1)
745           
746           # 前月の時刻を元に、ディフォルト書式を生成します
747           db["logpath"] = FEEDXMLDIR + prevmonth.strftime("%Y%m") + ".xml"
748           db["logdisplay"] = prevmonth.strftime("%Y年%m月").gsub("年0", "年")
749         end
750         
751         # インポートモードの場合の処理
752         when "import"
753         db["loglist"] = LogList.readxml(LISTXMLPATH)
754         
755         # リセットモードの場合の処理
756         when "reset"
757         case params["action"]
758           # リセット実行時の処理
759           when "exec"
760           file = FileUploader.new
761           file.filelist().each { |fname|
762             file.delete(fname) if File.ftype(XMLPATH + fname) == "file"
763           }
764           
765           # 全ファイルの初期化を実行する
766           # loglist.xmlの初期化
767           loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}diary.xml"], LISTXMLPATH)
768           loglist.to_xml
769           writer = FeedWriter.new(XMLPATH + loglist.path[0].match(/[^\/]*?$/).to_a[0])
770           writer.to_xml(Feed.new({}), [])
771           
772           db["loglist"] = LogList.readxml(LISTXMLPATH)
773           
774           # diary.xmlの初期化
775           writer = FeedWriter.new(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0])
776           writer.to_xml(Feed.new({}), [])
777           
778           # 初期の編集ファイルはloglist.xml上の最も上のファイル
779           session["filepath"] = db["loglist"].path[0]
780           db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
781           db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
782           
783           # 画面の遷移先をトップページにする
784           params["mode"] = ""
785           params["action"] = ""
786           # パラメタ無しの処理
787         else
788           # ファイル一覧を取得する
789           filelist = FileUploader.new.filelist()
790           
791           # タイプがファイルのみリストとして取得する
792           db["filelist"] = []
793           filelist.each { |fname| db["filelist"] << fname if File.ftype(XMLPATH + fname) == "file"}
794         end
795         
796         # ログイン時の画面
797       else
798         # loglist.xmlが存在するかチェック
799         if File.exist?(LISTXMLPATH) == false
800           # なかった場合はloglist.xmlを自動生成
801           loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}diary.xml"], LISTXMLPATH)
802           loglist.to_xml
803           writer = FeedWriter.new(XMLPATH + loglist.path[0].match(/[^\/]*?$/).to_a[0])
804           writer.to_xml(Feed.new({}), [])
805         end
806         
807         db["loglist"] = LogList.readxml(LISTXMLPATH)
808         
809         # diary.xmlが存在するかチェック
810         if File.exist?(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0]) == false
811           # なかった場合はdiary.xmlを自動生成
812           writer = FeedWriter.new(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0])
813           writer.to_xml(Feed.new({}), [])
814         end
815         
816         # 初期の編集ファイルはloglist.xml上の最も上のファイル
817         session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
818         db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
819         db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
820       end
821     end
822   end
823   
824   # マルチパートフォームの場合の処理です
825   def Controller.MultiForm(cgi, session, params, db)
826     db.transaction do
827       case params["mode"]
828         # 特定位置に挿入する場合の処理
829         when "insert"
830         case params["action"]
831           when "exec"
832           # ファイルを実際にアップロードします
833           file = FileUploader.new
834           file.upload(db["logpath"], db["importxml"])
835           
836           # loglist.xmlを更新します
837           db["loglist"].path[db["loginsertindex"], 0] = db["logpath"]
838           db["loglist"].display[db["loginsertindex"], 0] = db["logdisplay"]
839           db["loglist"].to_xml
840           
841           # 情報を更新します
842           db["loglist"] = LogList.readxml(LISTXMLPATH)
843           session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
844           db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
845           db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
846           
847           when "confirm"
848           # 入力されたログファイルパスが既に存在するかを確認
849           checkflag = true
850           db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
851           db["loglist"].path.each do |val|
852             if val == db["logpath"]
853               # 重複していた場合エラーメッセージを表示し、処理を行わない
854               db["logpath"] = ""
855               params["action"] = ""
856               db["error"] = "<br><span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
857               checkflag = false
858               break;
859             end
860           end
861           
862           if checkflag == true
863             db["loginsertindex"] = cgi["loginsertindex"].read.to_i
864             db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
865             db["importxml"] = cgi["updata"].read
866             
867             # XMLの整合性をチェックします
868             begin
869               REXML::Document.new(Controller.fix_updata_enc(db["importxml"].to_s))
870             rescue => exception
871               db["error"] = "<br><span style='color: #ff0000'>不正な整形のXMLです!ファイルを見直してください。</span><br>"
872               db["error"] << "<div class='divstyle' style='width: 560px; color: #ff0000; text-align: left;'>"
873               db["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
874               db["error"] << "</div>"
875               params["action"] = ""
876             end
877           end
878           
879           # 0位置への挿入防止
880           if db["loginsertindex"] == 0
881             params["action"] = ""
882             db["error"] = "<br><span style='color: #ff0000'>ラジオボックスでログの挿入位置を選択してください!</span><br>"
883           end
884           
885           if db["logpath"] == FEEDXMLDIR || db["logdisplay"].blank?
886             params["action"] = ""
887             db["error"] = "<br><span style='color: #ff0000'>インポートファイル、及び、ログの表示名は空欄にできません。</span><br>"
888           end
889         else
890           # loglist.xmlをロードします
891           db["loglist"] = LogList.readxml(LISTXMLPATH)
892         end
893         
894         # diary.xmlを入れ替える処理
895         when "replace"
896         case params["action"]
897           when "exec"
898           # diary.xmlを移動します
899           FileUtils.move(XMLPATH + "diary.xml", XMLPATH + db["logpath"].match(/[^\/]*?$/).to_s)
900           # ファイルをアップロードします
901           file = FileUploader.new
902           file.upload("diary.xml", db["importxml"])
903           
904           # loglist.xmlを更新します
905           db["loglist"].path[1, 0] = db["logpath"]
906           db["loglist"].display[1, 0] = db["logdisplay"]
907           db["loglist"].to_xml
908           
909           # 情報を更新します
910           db["loglist"] = LogList.readxml(LISTXMLPATH)
911           session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
912           db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
913           db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
914           
915           when "confirm"
916           # 入力されたログファイルパスが既に存在するかを確認
917           checkflag = true
918           db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
919           db["loglist"].path.each do |val|
920             if val == db["logpath"]
921               # 重複していた場合エラーメッセージを表示し、処理を行わない
922               params["action"] = ""
923               db["error"] = "<br><span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
924               checkflag = false
925               break;
926             end
927           end
928           
929           if checkflag == true
930             db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
931             db["importxml"] = cgi["updata"].read
932             
933             # XMLの整合性をチェックします
934             begin
935               REXML::Document.new(Controller.fix_updata_enc(db["importxml"].to_s))
936             rescue => exception
937               db["error"] = "<br><span style='color: #ff0000'>不正な整形のXMLです!ファイルを見直してください。</span><br>"
938               db["error"] << "<div class='divstyle' style='width: 560px; color: #ff0000; text-align: left;'>"
939               db["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
940               db["error"] << "</div>"
941               params["action"] = ""
942             end
943           end
944           
945           if db["logpath"].blank? || db["logdisplay"].blank? || db["importxml"].blank?
946             params["action"] = ""
947             db["error"] = "<span style='color: #ff0000'>インポートファイル、及び、ログの表示名、ログのパスは空欄にできません。</span><br>"
948           end
949         else
950           # loglist.xmlをロードします
951           db["loglist"] = LogList.readxml(LISTXMLPATH)
952         end
953       else
954         # loglist.xmlをロードします
955         db["loglist"] = LogList.readxml(LISTXMLPATH)
956       end
957     end
958   end
959   
960   # Formのenctypeがmultypartだった場合、入力された値をrubyバージョンに左右されずに読み取るメソッドです。
961   def Controller.get_mpart_value(cgi_param)
962     if RUBY_VERSION >= "1.9.0"
963       cgi_param[0..cgi_param.length].to_s
964     else
965       cgi_param.read.to_s
966     end
967   end
968   
969   # アップロードされたXMLをバージョンに左右されずに読み取るために、ruby-1.9.1以上ではエンコーディングを強制指定します
970   def Controller.fix_updata_enc(file_to_s)
971     if RUBY_VERSION >= "1.9.0"
972       file_to_s.force_encoding("UTF-8")
973     else
974       file_to_s
975     end
976   end
977   
978 end
979
980 def main
981   # SESSION変数、パラメータなどを取得します
982   cgi = CGI.new
983   session = CGI::Session.new(cgi)
984   params = Hash[*cgi.params.to_a.map{|k, v| [k, v[0].to_s]}.flatten]
985   
986   # コントローラー部分
987   # セッション管理
988   if session["login"] != "true"
989     if (cgi["loginid"] == LOGINID && cgi["password"] == PASSWORD)
990       session["login"] = "true"
991       
992       # ワークフォルダの中をクリーンアップします
993       filelist = Dir::entries("./work")
994       # 削除条件 : 最終変更日時から1日(60*60*24sec)かつ、ファイルタイプがファイルの場合
995       filelist.each do |file|
996         File.delete("./work/#{file}") if Time.now - File.ctime("./work/#{file}") > 86400 && File.ftype("./work/#{file}") == "file"
997       end
998     end
999   end
1000   
1001   # ログアウト処理
1002   if params["mode"] == "logout"
1003     session["login"] = nil
1004     session["logini"] = nil
1005     session["password"] = nil
1006     session.delete
1007   end
1008   
1009   begin
1010     # メインコントローラー
1011     # セッションが有効な場合のも実行します
1012     if session["login"] == "true"
1013       # PStore破損チェック!
1014       begin
1015         db = PStore.new("./work/#{session.session_id}.dat")
1016         db.transaction do
1017           db["error"] = ""
1018         end
1019       rescue
1020         # PStoreファイルを破棄する
1021         File.delete("./work/#{session.session_id}.dat")
1022         # PStoreが破損していた場合はセッション情報を破棄する
1023         session["login"] = nil
1024         session["logini"] = nil
1025         session["password"] = nil
1026         session.delete
1027       end
1028       
1029       # フォームによって挙動を変更します
1030       if cgi["mode"].respond_to?(:read) && cgi["action"].respond_to?(:read)
1031         params["mode"] = cgi["mode"].read
1032         params["action"] = cgi["action"].read
1033         Controller.MultiForm(cgi, session, params, db)
1034       else
1035         Controller.NormalForm(cgi, session, params, db)
1036       end
1037     end
1038     
1039     # メニューとして表示されるHTML文字列です
1040     if USEFILEMANAGER == true
1041       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>"
1042     else
1043       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>"
1044     end
1045     
1046     # ビュー部分
1047     # modeとactionの相関図は以下のようになります。
1048     # [mode] + [action]
1049     #        + [action]
1050     #        + [action]
1051     #        + ... and more
1052     if session["login"] != "true"
1053       # セッションが存在しない場合は強制的にトップページに遷移
1054       htmlwriter = HtmlWriter.new("./erbtemp/login.html.erb", binding)
1055     else
1056       case params["mode"]
1057         when "selectlog"
1058         htmlwriter = HtmlWriter.new("./erbtemp/select.html.erb", binding)
1059         when "newentry"
1060         htmlwriter = HtmlWriter.new("./erbtemp/newentry.html.erb", binding)
1061         when "editentry"
1062         htmlwriter = HtmlWriter.new("./erbtemp/editentry.html.erb", binding)
1063         when "delentry"
1064         htmlwriter = HtmlWriter.new("./erbtemp/delentry.html.erb", binding)
1065         when "editfeed"
1066         htmlwriter = HtmlWriter.new("./erbtemp/editfeed.html.erb", binding)
1067         when "log"
1068         htmlwriter = HtmlWriter.new("./erbtemp/log.html.erb", binding)
1069         when "insert"
1070         htmlwriter = HtmlWriter.new("./erbtemp/insertfeed.html.erb", binding)
1071         when "replace"
1072         htmlwriter = HtmlWriter.new("./erbtemp/replacefeed.html.erb", binding)
1073         when "import"
1074         htmlwriter = HtmlWriter.new("./erbtemp/indeximport.html.erb", binding)
1075         when "reset"
1076         htmlwriter = HtmlWriter.new("./erbtemp/reset.html.erb", binding)
1077       else
1078         htmlwriter = HtmlWriter.new("./erbtemp/index.html.erb", binding)
1079       end
1080     end
1081   rescue => exception
1082     # エラーが発生した場合、それを画面に表示します
1083     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
1084   end
1085   
1086   # 実際にHTMLを出力します
1087   begin
1088     cgi.out{htmlwriter.to_code}
1089   rescue => exception
1090     # エラーが発生した場合、それを画面に表示します
1091     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
1092     cgi.out{htmlwriter.to_code}
1093   end
1094 end
1095
1096 begin
1097   main
1098 rescue => evar
1099   # エラーが発生した場合、それを画面に表示します
1100   detail = ("%s: %s (%s)\n" %
1101   [evar.backtrace[0], evar.message, evar.send('class')]) +
1102   evar.backtrace[1..-1].join("\n")
1103   puts "content-type: text/html\n\n<plaintext>\n" + detail
1104 end