OSDN Git Service

modified reset.html.erb.
[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)(?:&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!(/<br>/i, "\n") if REPLACEBRTAG
366     str.gsub!(/(<br>|<\/p>|<\/h\d>)(?=[^\n])/i) { "#$1\n" } unless REPLACEBRTAG
367     str.gsub!(/(<[^>]*?)#{Regexp.escape(XMLPATH)}/) { "#$1#{FEEDXMLDIR}" }
368     CGI.escapeHTML(str)
369   end
370   
371   # 確認画面で表示されるCONTENT要素を生成するメソッドです
372   def content_for_view
373     str = @attr[:content].dup
374     str = CGI.unescapeHTML(str)
375     str.strip!
376     str.gsub!(/<br>/i, "\n") if REPLACEBRTAG
377     str.gsub!(/(<[^>]*?)#{Regexp.escape(FEEDXMLDIR)}/) { "#$1#{XMLPATH}" }
378     str
379   end
380   
381   # 内部に保持している情報を、Atom Feed1.0形式の文字列に出力するメソッドです
382   def to_s
383     buf = []
384     buf.push("<entry>")
385     buf.push("<id>#{@attr[:entryid]}</id>")
386     buf.push("<title>#{@attr[:title]}</title>")
387     buf.push("<summary>#{@attr[:summary]}</summary>")
388     buf.push("<published>#{@attr[:published]}</published>")
389     buf.push("<updated>#{@attr[:updated]}</updated>")
390     buf.push("<link href=\"#{@attr[:url]}\" />")
391     buf.push("<content type=\"html\">#{@attr[:content]}</content>")
392     buf.push("#{CGI.unescapeHTML(@attr[:others])}") if @attr[:others] != ""
393     buf.push("</entry>")
394     
395     return buf.join("\n")
396   end
397 end
398
399 # = FeedWriterクラス
400 #
401 # Atom Feed 1.0 XMLファイルに書き込みを行うクラスです
402 class FeedWriter
403   # 初期化メソッドです
404   #
405   # _path_ :: Atom Feed 1.0 XMLファイルの書き込みパス
406   def initialize(path)
407     @path = path
408   end
409   
410   # XMLファイル出力用メソッドです
411   #
412   # _feed_ :: Feedオブジェクト
413   # _entry_ :: Entryオブジェクトの配列
414   def to_xml(feed, entrylist)
415     buf = []
416     buf.push("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
417     buf.push("#{feed.to_s}\n")
418     entrylist.each { |entry|
419       buf.push("#{entry.to_s}\n")
420     }
421     buf.push("</feed>")
422     
423     myopen(@path, "w") do |f|
424       f.print buf.join("\n")
425     end
426   end
427 end
428
429 # = HtmlWriterクラス
430
431 # テンプレートファイル(*.erb)を読み込み、管理するクラスです
432 class HtmlWriter
433   # 初期化メソッドです
434   #
435   # _template_ :: テンプレートファイル(*.erb)のパス
436   # _binding_ :: binding変数
437   def initialize(template, binding)
438     @erb = ERB.new(myopen(template, "r:utf-8") {|f| f.read}, nil, "-")
439     @binding = binding
440   end
441   
442   # テンプレートファイルの文字列を返却するメソッドです
443   def to_code
444     @erb.result(@binding)
445   end
446 end
447
448 # = LogListクラス
449 #
450 # loglist.xmlにかかわる入出力を行います
451 class LogList
452   # 初期化メソッドです
453   #
454   # _display_ :: 画面表示用文字の配列
455   # _path_ :: XMLファイルパスの配列
456   # _logpath_ :: loglist.xmlのパス
457   def initialize(display, path, logpath)
458     @display = display
459     @path = path
460     @logpath = logpath
461   end
462   
463   attr_accessor :display, :path, :logpath
464   
465   # loglist.xmlファイルを読み込んで解析し、LogListオブジェクトを返却するメソッドです
466   #
467   # _logpath_ :: loglist.xmlへのパス
468   def LogList.readxml(logpath)
469     
470     # ファイルを読み込みます
471     lines = []
472     myopen(logpath, "r:utf-8") { |f|
473       lines = f.readlines
474     }
475     
476     doc = REXML::Document.new(lines.join("\n"))
477     @display = []
478     @path = []
479     
480     doc.elements.each("list/file") { |elm|
481       elm.elements.each {|child|
482         case child.name
483           when "display"
484           @display.push(child.text)
485           when "path"
486           @path.push(child.text)
487         else 
488           # With no action
489         end
490       }
491     }
492     
493     return LogList.new(@display, @path, logpath)
494   end
495   
496   # loglist.xmlにオブジェクトの内容を反映するメソッドです
497   def to_xml
498     buf = []
499     buf.push("<list>")
500     path.each_with_index do |path, i|
501       buf.push("<file>")
502       buf.push("<display>#{@display[i]}</display>")
503       buf.push("<path>#{@path[i]}</path>")
504       buf.push("</file>")
505     end
506     buf.push("</list>")
507     
508     myopen(@logpath, "w") do |f|
509       f.print buf.join("\n")
510     end
511   end
512   
513 end
514
515 # = FileUploaderクラス
516 #
517 # 画像の管理を行うクラスです
518 class FileUploader
519   # 初期化メソッドです
520   def initialize
521   end
522   
523   # ファイルの一覧を取得し、ファイル名称を格納した配列を返却します
524   def filelist
525     arr = Dir::entries(XMLPATH).sort
526     arr.delete(".")
527     arr.delete("..")
528     
529     return arr
530   end
531   
532   # ファイルの消去を実行します
533   #
534   # _name_ :: 消去するファイル名
535   def delete(name)
536     File.delete(XMLPATH + name.match(/[^\/]*?$/).to_a[0])
537   end
538   
539   # ファイルのアップロードを実行します
540   #
541   # _name_ :: アップロードファイルの名称
542   # _img_ :: アップロードファイル
543   def upload(name, img)
544     open(XMLPATH + name.match(/[^\/]*?$/).to_a[0], "w") do |f|
545       f.binmode
546       f.write img
547     end
548   end
549 end
550
551 # = Controllerクラス
552 #
553 # コントローラ部分に相当する処理を受け持つクラスです
554 class Controller
555   def Controller.NormalForm(cgi, session, params, db)
556     db.transaction do
557       # modeとactionの相関図は以下のようになります。
558       # [mode] + [action]
559       #        + [action]
560       #        + [action]
561       #        + ... and more
562       case params["mode"]
563         # ログ選択画面
564         when "logselect"
565         session["filepath"] = params["logpath"]
566         db["loglist"] = LogList.readxml(LISTXMLPATH)
567         # 初期状態で選択されるログは「loglist.xml」の最上に位置するログになります
568         session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
569         db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
570         db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
571         
572         # 新規記事追加部分
573         when "newentry"
574         case params["action"]
575           # 確認画面
576           when "confirm"
577           db["newentry"] = Entry.new(params)
578           db["newentry"].content = db["newentry"].content_for_blog
579           # 記事の追記を実際にファイルに反映
580           when "exec"
581           db["entry"].unshift db["newentry"]
582           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
583           feedwriter.to_xml(db["feed"], db["entry"])
584         else 
585           # New Diary - Default
586         end
587         
588         # 記事編集部分
589         when "editentry"
590         case params["action"]
591           # 編集画面
592           when "edit"
593           db["editindex"] = cgi["editindex"].to_i
594           # 確認画面
595           when "confirm"
596           db["editentry"] = Entry.new(params)
597           db["editentry"].content = db["editentry"].content_for_blog
598           # 記事の変更を実際にファイルに反映
599           when "exec"
600           db["entry"][db["editindex"]] = db["editentry"]
601           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
602           feedwriter.to_xml(db["feed"], db["entry"])
603         else
604         end
605         
606         # 記事削除部分
607         when "delentry"
608         case params["action"]
609           # 確認画面
610           when "confirm"
611           db["delindex"] = cgi["delindex"].to_i
612           # 記事の削除を実際にファイルに反映
613           when "exec"
614           db["entry"].delete_at(db["delindex"])
615           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
616           feedwriter.to_xml(db["feed"], db["entry"])
617         end
618         
619         # Feed情報変更部分
620         when "editfeed"
621         case params["action"]
622           # 確認画面
623           when "confirm"
624           db["feed"] = Feed.new(params)
625           # 実際にFeed情報の変更をファイルに反映
626           when "exec"
627           feedwriter = FeedWriter.new(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0])
628           feedwriter.to_xml(db["feed"], db["entry"])
629         else
630           
631         end
632         
633         # ログ編集モード
634         when "log"
635         case params["action"]
636           # ログファイルの編集を実際にファイルに反映
637           when "addexec"
638           # 入力された内容を保持する配列に追加
639           db["loglist"].path[1, 0] = db["logpath"]
640           db["loglist"].display[1, 0] = db["logdisplay"]
641           db["loglist"].to_xml
642           # 既存のdiary.xmlを指定された名称でコピーして保存
643           FileUtils.copy_file(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0], XMLPATH + db["logpath"].match(/[^\/]*?$/).to_a[0], true)
644           # 保持している情報を更新する
645           db["loglist"] = LogList.readxml(LISTXMLPATH)
646           db["entry"] = []
647           # 新たなdiary.xmlを生成。この際保持する情報は同一のFeedオブジェクト
648           writer = FeedWriter.new(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0])
649           writer.to_xml(db["feed"], [])
650           # 確認画面
651           when "addconfirm"
652           # 入力されたログが既に存在するかを確認
653           db["loglist"].path.each do |val|
654             if val == cgi["logpath"]
655               # 重複していた場合エラーメッセージを表示し、処理を行わない
656               params["action"] = ""
657               error = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
658               break;
659             end
660           end
661           
662           db["logpath"] = cgi["logpath"]
663           db["logdisplay"] = cgi["logdisplay"]
664           
665           if db["logpath"].blank? || db["logdisplay"].blank?
666             params["action"] = ""
667           end
668           when "back"
669           # 削除確認画面
670           when "delconfirm"
671           db["logdelindex"] = params["logdelindex"].to_i
672           
673           if db["logdelindex"] == 0
674             params["action"] = ""
675           end
676           # 削除処理
677           when "delexec"
678           if db["logdelindex"] == 0
679             params["action"] = ""
680           else
681             # 記事ファイルを削除します
682             File.delete(XMLPATH + db["loglist"].path[db["logdelindex"]].match(/[^\/]*?$/).to_s)
683             # ログリストから削除します
684             db["loglist"].path.delete_at(db["logdelindex"])
685             db["loglist"].display.delete_at(db["logdelindex"])
686             db["loglist"].to_xml
687             # 保持している情報を更新する
688             db["loglist"] = LogList.readxml(LISTXMLPATH)
689             db["entry"] = []
690           end
691           # 編集画面
692           when "edit"
693           db["logeditindex"] = params["logdelindex"].to_i
694           
695           db["logpath"] = db["loglist"].path[db["logeditindex"]]
696           db["logdisplay"] = db["loglist"].display[db["logeditindex"]]
697           
698           if db["logeditindex"] == 0
699             params["action"] = ""
700           end
701           # 編集確認画面
702           when "editconfirm"
703           
704           checkflag = true
705           db["loglist"].path.each_with_index do |val, i|
706             if db["logeditindex"] != i
707               if params["logpath"].to_s == db["loglist"].path[i].to_s
708                 checkflag = false
709               end
710             end
711           end
712           
713           if checkflag == false
714             params["action"] = "edit"
715             db["error"] = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
716           else
717             db["loginsertindex"] = params["loginsertindex"].to_i
718             
719             db["logpath"] = params["logpath"].to_s
720             db["logdisplay"] = params["logdisplay"].to_s
721           end
722           # 編集実行
723           when "editexec"
724           # ファイルを移動します
725           if XMLPATH + db["loglist"].path[db["logeditindex"]].match(/[^\/]*?$/).to_s != XMLPATH + db["logpath"].match(/[^\/]*?$/).to_s
726             FileUtils.move(XMLPATH + db["loglist"].path[db["logeditindex"]].match(/[^\/]*?$/).to_s, XMLPATH + db["logpath"].match(/[^\/]*?$/).to_s)
727           end
728           # ログリストを更新します
729           db["loglist"].path.delete_at(db["logeditindex"])
730           db["loglist"].display.delete_at(db["logeditindex"])
731           db["loglist"].path.insert(db["loginsertindex"] + 1, db["logpath"])
732           db["loglist"].display.insert(db["loginsertindex"] + 1, db["logdisplay"])
733           db["loglist"].to_xml
734           # 初期表示画面
735         else
736           # 初期表示の際に内部保持データをリフレッシュ
737           db["loglist"] = LogList.readxml(LISTXMLPATH)
738           
739           # 現在編集中のデータを強制的に最上のファイルパスに変更
740           session["filepath"] = db["loglist"].path[0]
741           
742           # 前月の時刻を作成します
743           prevmonth = (DateTime.now << 1)
744           
745           # 前月の時刻を元に、ディフォルト書式を生成します
746           db["logpath"] = FEEDXMLDIR + prevmonth.strftime("%Y%m") + ".xml"
747           db["logdisplay"] = prevmonth.strftime("%Y年%m月").gsub("年0", "年")
748         end
749         
750         # インポートモードの場合の処理
751         when "import"
752         db["loglist"] = LogList.readxml(LISTXMLPATH)
753         
754         # リセットモードの場合の処理
755         when "reset"
756         case params["action"]
757           # リセット実行時の処理
758           when "exec"
759           file = FileUploader.new
760           file.filelist().each { |fname|
761             file.delete(fname) if File.ftype(XMLPATH + fname) == "file"
762           }
763           
764           # 全ファイルの初期化を実行する
765           # loglist.xmlの初期化
766           loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}diary.xml"], LISTXMLPATH)
767           loglist.to_xml
768           writer = FeedWriter.new(XMLPATH + loglist.path[0].match(/[^\/]*?$/).to_a[0])
769           writer.to_xml(Feed.new({}), [])
770           
771           db["loglist"] = LogList.readxml(LISTXMLPATH)
772           
773           # diary.xmlの初期化
774           writer = FeedWriter.new(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0])
775           writer.to_xml(Feed.new({}), [])
776           
777           # 初期の編集ファイルはloglist.xml上の最も上のファイル
778           session["filepath"] = db["loglist"].path[0]
779           db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
780           db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
781           
782           # 画面の遷移先をトップページにする
783           params["mode"] = ""
784           params["action"] = ""
785           # パラメタ無しの処理
786         else
787           # ファイル一覧を取得する
788           filelist = FileUploader.new.filelist()
789           
790           # タイプがファイルのみリストとして取得する
791           db["filelist"] = []
792           filelist.each { |fname| db["filelist"] << fname if File.ftype(XMLPATH + fname) == "file"}
793         end
794         
795         # ログイン時の画面
796       else
797         # loglist.xmlが存在するかチェック
798         if File.exist?(LISTXMLPATH) == false
799           # なかった場合はloglist.xmlを自動生成
800           loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}diary.xml"], LISTXMLPATH)
801           loglist.to_xml
802           writer = FeedWriter.new(XMLPATH + loglist.path[0].match(/[^\/]*?$/).to_a[0])
803           writer.to_xml(Feed.new({}), [])
804         end
805         
806         db["loglist"] = LogList.readxml(LISTXMLPATH)
807         
808         # diary.xmlが存在するかチェック
809         if File.exist?(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0]) == false
810           # なかった場合はdiary.xmlを自動生成
811           writer = FeedWriter.new(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0])
812           writer.to_xml(Feed.new({}), [])
813         end
814         
815         # 初期の編集ファイルはloglist.xml上の最も上のファイル
816         session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
817         db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
818         db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
819       end
820     end
821   end
822   
823   # マルチパートフォームの場合の処理です
824   def Controller.MultiForm(cgi, session, params, db)
825     db.transaction do
826       case params["mode"]
827         # 特定位置に挿入する場合の処理
828         when "insert"
829         case params["action"]
830           when "exec"
831           # ファイルを実際にアップロードします
832           file = FileUploader.new
833           file.upload(db["logpath"], db["importxml"])
834           
835           # loglist.xmlを更新します
836           db["loglist"].path[db["loginsertindex"], 0] = db["logpath"]
837           db["loglist"].display[db["loginsertindex"], 0] = db["logdisplay"]
838           db["loglist"].to_xml
839           
840           # 情報を更新します
841           db["loglist"] = LogList.readxml(LISTXMLPATH)
842           session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
843           db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
844           db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
845           
846           when "confirm"
847           # 入力されたログファイルパスが既に存在するかを確認
848           checkflag = true
849           db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
850           db["loglist"].path.each do |val|
851             if val == db["logpath"]
852               # 重複していた場合エラーメッセージを表示し、処理を行わない
853               db["logpath"] = ""
854               params["action"] = ""
855               db["error"] = "<br><span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
856               checkflag = false
857               break;
858             end
859           end
860           
861           if checkflag == true
862             db["loginsertindex"] = cgi["loginsertindex"].read.to_i
863             db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
864             db["importxml"] = cgi["updata"].read
865             
866             # XMLの整合性をチェックします
867             begin
868               REXML::Document.new(Controller.fix_updata_enc(db["importxml"].to_s))
869             rescue => exception
870               db["error"] = "<br><span style='color: #ff0000'>不正な整形のXMLです!ファイルを見直してください。</span><br>"
871               db["error"] << "<div class='divstyle' style='width: 560px; color: #ff0000; text-align: left;'>"
872               db["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
873               db["error"] << "</div>"
874               params["action"] = ""
875             end
876           end
877           
878           # 0位置への挿入防止
879           if db["loginsertindex"] == 0
880             params["action"] = ""
881             db["error"] = "<br><span style='color: #ff0000'>ラジオボックスでログの挿入位置を選択してください!</span><br>"
882           end
883           
884           if db["logpath"] == FEEDXMLDIR || db["logdisplay"].blank?
885             params["action"] = ""
886             db["error"] = "<br><span style='color: #ff0000'>インポートファイル、及び、ログの表示名は空欄にできません。</span><br>"
887           end
888         else
889           # loglist.xmlをロードします
890           db["loglist"] = LogList.readxml(LISTXMLPATH)
891         end
892         
893         # diary.xmlを入れ替える処理
894         when "replace"
895         case params["action"]
896           when "exec"
897           # diary.xmlを移動します
898           FileUtils.move(XMLPATH + "diary.xml", XMLPATH + db["logpath"].match(/[^\/]*?$/).to_s)
899           # ファイルをアップロードします
900           file = FileUploader.new
901           file.upload("diary.xml", db["importxml"])
902           
903           # loglist.xmlを更新します
904           db["loglist"].path[1, 0] = db["logpath"]
905           db["loglist"].display[1, 0] = db["logdisplay"]
906           db["loglist"].to_xml
907           
908           # 情報を更新します
909           db["loglist"] = LogList.readxml(LISTXMLPATH)
910           session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
911           db["feed"] = Feed.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
912           db["entry"] = Entry.readxml(XMLPATH + session["filepath"].match(/[^\/]*?$/).to_a[0].to_s)
913           
914           when "confirm"
915           # 入力されたログファイルパスが既に存在するかを確認
916           checkflag = true
917           db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
918           db["loglist"].path.each do |val|
919             if val == db["logpath"]
920               # 重複していた場合エラーメッセージを表示し、処理を行わない
921               params["action"] = ""
922               db["error"] = "<br><span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
923               checkflag = false
924               break;
925             end
926           end
927           
928           if checkflag == true
929             db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
930             db["importxml"] = cgi["updata"].read
931             
932             # XMLの整合性をチェックします
933             begin
934               REXML::Document.new(Controller.fix_updata_enc(db["importxml"].to_s))
935             rescue => exception
936               db["error"] = "<br><span style='color: #ff0000'>不正な整形のXMLです!ファイルを見直してください。</span><br>"
937               db["error"] << "<div class='divstyle' style='width: 560px; color: #ff0000; text-align: left;'>"
938               db["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
939               db["error"] << "</div>"
940               params["action"] = ""
941             end
942           end
943           
944           if db["logpath"].blank? || db["logdisplay"].blank? || db["importxml"].blank?
945             params["action"] = ""
946             db["error"] = "<span style='color: #ff0000'>インポートファイル、及び、ログの表示名、ログのパスは空欄にできません。</span><br>"
947           end
948         else
949           # loglist.xmlをロードします
950           db["loglist"] = LogList.readxml(LISTXMLPATH)
951         end
952       else
953         # loglist.xmlをロードします
954         db["loglist"] = LogList.readxml(LISTXMLPATH)
955       end
956     end
957   end
958   
959   # Formのenctypeがmultypartだった場合、入力された値をrubyバージョンに左右されずに読み取るメソッドです。
960   def Controller.get_mpart_value(cgi_param)
961     if RUBY_VERSION >= "1.9.0"
962       cgi_param[0..cgi_param.length].to_s
963     else
964       cgi_param.read.to_s
965     end
966   end
967   
968   # アップロードされたXMLをバージョンに左右されずに読み取るために、ruby-1.9.1以上ではエンコーディングを強制指定します
969   def Controller.fix_updata_enc(file_to_s)
970     if RUBY_VERSION >= "1.9.0"
971       file_to_s.force_encoding("UTF-8")
972     else
973       file_to_s
974     end
975   end
976   
977 end
978
979 def main
980   # SESSION変数、パラメータなどを取得します
981   cgi = CGI.new
982   session = CGI::Session.new(cgi)
983   params = Hash[*cgi.params.to_a.map{|k, v| [k, v[0].to_s]}.flatten]
984   
985   # コントローラー部分
986   # セッション管理
987   if session["login"] != "true"
988     if (cgi["loginid"] == LOGINID && cgi["password"] == PASSWORD)
989       session["login"] = "true"
990       
991       # ワークフォルダの中をクリーンアップします
992       filelist = Dir::entries("./work")
993       # 削除条件 : 最終変更日時から1日(60*60*24sec)かつ、ファイルタイプがファイルの場合
994       filelist.each do |file|
995         File.delete("./work/#{file}") if Time.now - File.ctime("./work/#{file}") > 86400 && File.ftype("./work/#{file}") == "file"
996       end
997     end
998   end
999   
1000   # ログアウト処理
1001   if params["mode"] == "logout"
1002     session["login"] = nil
1003     session["logini"] = nil
1004     session["password"] = nil
1005     session.delete
1006   end
1007   
1008   begin
1009     # メインコントローラー
1010     # セッションが有効な場合のも実行します
1011     if session["login"] == "true"
1012       # PStore破損チェック!
1013       begin
1014         db = PStore.new("./work/#{session.session_id}.dat")
1015         db.transaction do
1016           db["error"] = ""
1017         end
1018       rescue
1019         # PStoreファイルを破棄する
1020         File.delete("./work/#{session.session_id}.dat")
1021         # PStoreが破損していた場合はセッション情報を破棄する
1022         session["login"] = nil
1023         session["logini"] = nil
1024         session["password"] = nil
1025         session.delete
1026       end
1027       
1028       # フォームによって挙動を変更します
1029       if cgi["mode"].respond_to?(:read) && cgi["action"].respond_to?(:read)
1030         params["mode"] = cgi["mode"].read
1031         params["action"] = cgi["action"].read
1032         Controller.MultiForm(cgi, session, params, db)
1033       else
1034         Controller.NormalForm(cgi, session, params, db)
1035       end
1036     end
1037     
1038     # メニューとして表示されるHTML文字列です
1039     if USEFILEMANAGER == true
1040       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>"
1041     else
1042       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>"
1043     end
1044     
1045     # ビュー部分
1046     # modeとactionの相関図は以下のようになります。
1047     # [mode] + [action]
1048     #        + [action]
1049     #        + [action]
1050     #        + ... and more
1051     if session["login"] != "true"
1052       # セッションが存在しない場合は強制的にトップページに遷移
1053       htmlwriter = HtmlWriter.new("./erbtemp/login.html.erb", binding)
1054     else
1055       case params["mode"]
1056         when "selectlog"
1057         htmlwriter = HtmlWriter.new("./erbtemp/select.html.erb", binding)
1058         when "newentry"
1059         htmlwriter = HtmlWriter.new("./erbtemp/newentry.html.erb", binding)
1060         when "editentry"
1061         htmlwriter = HtmlWriter.new("./erbtemp/editentry.html.erb", binding)
1062         when "delentry"
1063         htmlwriter = HtmlWriter.new("./erbtemp/delentry.html.erb", binding)
1064         when "editfeed"
1065         htmlwriter = HtmlWriter.new("./erbtemp/editfeed.html.erb", binding)
1066         when "log"
1067         htmlwriter = HtmlWriter.new("./erbtemp/log.html.erb", binding)
1068         when "insert"
1069         htmlwriter = HtmlWriter.new("./erbtemp/insertfeed.html.erb", binding)
1070         when "replace"
1071         htmlwriter = HtmlWriter.new("./erbtemp/replacefeed.html.erb", binding)
1072         when "import"
1073         htmlwriter = HtmlWriter.new("./erbtemp/indeximport.html.erb", binding)
1074         when "reset"
1075         htmlwriter = HtmlWriter.new("./erbtemp/reset.html.erb", binding)
1076       else
1077         htmlwriter = HtmlWriter.new("./erbtemp/index.html.erb", binding)
1078       end
1079     end
1080   rescue => exception
1081     # エラーが発生した場合、それを画面に表示します
1082     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
1083   end
1084   
1085   # 実際にHTMLを出力します
1086   begin
1087     cgi.out{htmlwriter.to_code}
1088   rescue => exception
1089     # エラーが発生した場合、それを画面に表示します
1090     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
1091     cgi.out{htmlwriter.to_code}
1092   end
1093 end
1094
1095 begin
1096   main
1097 rescue => evar
1098   # エラーが発生した場合、それを画面に表示します
1099   detail = ("%s: %s (%s)\n" %
1100   [evar.backtrace[0], evar.message, evar.send('class')]) +
1101   evar.backtrace[1..-1].join("\n")
1102   puts "content-type: text/html\n\n<plaintext>\n" + detail
1103 end