changeset 2:1110baebe9a9 draft

RCS-revision 1.3 date: 2004/01/01 09:15:50; author: yuuji; state: Exp; lines: +400 -170 * Make it possible to select the number of days for displaying schedule. * In personal mode, display schedule contents instead of the name in monthly table.
author HIROSE Yuuji <yuuji@gentei.org>
date Thu, 01 Jan 2004 09:15:50 +0859
parents 8145b15d3d6f
children 354e09bb8ce1
files after5.rb
diffstat 1 files changed, 401 insertions(+), 171 deletions(-) [+]
line wrap: on
line diff
--- a/after5.rb	Mon Dec 29 17:01:20 2003 +0859
+++ b/after5.rb	Thu Jan 01 09:15:50 2004 +0859
@@ -1,9 +1,9 @@
 #!/usr/local/bin/ruby
 #
 # Associative Scheduling Table - after5
-# (C)2003 by HIROSE Yuuji [yuuji@gentei.org]
-# $Id: after5.rb,v 1.2 2003/12/29 17:01:20 yuuji Exp $
-# Last modified Tue Dec 30 01:48:15 2003 on firestorm
+# (C)2003, 2004 by HIROSE Yuuji [yuuji@gentei.org]
+# $Id: after5.rb,v 1.3 2004/01/01 09:15:50 yuuji Exp $
+# Last modified Thu Jan  1 18:11:47 2004 on firestorm
 # See http://www.gentei.org/~yuuji/software/after5/
 # このスクリプトはEUCで保存してください。
 
@@ -105,15 +105,16 @@
     submit(name, "GO")+reset(name, "Reset")
   end
 
-  def select_integer(name, b, e, selected=nil)
-    start = (b<e ? b : e)
-    last  = (b>e ? b : e)
+  def select(name, range, selected=nil)
+    #start = (b<e ? b : e)
+    #last  = (b>e ? b : e)
+    c=0
     "<select name=\"#{name}\">\n" + \
-    (start..last).collect{|i|
-      sprintf "<option%s>%d%s",
+    range.collect{|i|
+      sprintf "<option%s>%s%s",
 	(selected.to_s==i.to_s) ? " selected" : "", 
-	i,
-	i%6==0 ? "\n" : ''
+	i.to_s,
+	(c+=1)%6==0 ? "\n" : ''
     }.join + \
     "\n</select>\n"
   end
@@ -208,6 +209,10 @@
     Dir.mkdir(d) unless test(?d, d)
     file = File.join(d, attr)
     begin
+      unless @usermap[user]
+	@usermap[user] = {}
+	mkdir_p(d) unless test(?d, d)
+      end
       @usermap[user][attr] = text
       if text==nil
 	File.unlink(file)
@@ -316,8 +321,9 @@
   def groups()
     @groupmap.keys
   end
-  def addgroup(grp, users, remove=nil, role='members')
-    return nil unless @groupmap[grp]
+  def addgroup(group, users, remove=nil, role='members')
+    grp = groups.grep(group)[0]	# group may be tainted, using kept name
+    return nil unless grp
     for u in users
       next unless account_exists(u)
       mdir = File.join(@groupmapdir, grp, role)
@@ -349,8 +355,11 @@
     name
   end
   def creategroup(grp, grpname="", admin=[])
-    return nil unless /^[-A-Z0-9._:!$%,]+$/i =~ grp
-    grp.untaint
+    grpptnOK = /^[-A-Z0-9._:!$%,]+$/i
+    return nil unless grpptnOK =~ grp
+    gg = ''
+    grp.split('').each{|c| gg << c[0].chr if c =~ grpptnOK}
+    grp = gg
     gdir = File.join(@groupmapdir, grp)
     mkdir_p(gdir)		# Should not care errors here
     Dir.mkdir(File.join(gdir, "admin"))
@@ -402,6 +411,7 @@
   def rm_rf(path)
     if (list = Dir.glob(path))[0]
       for p in list
+	p.untaint
 	system "/bin/rm -rf \"#{p}\""
       end
       cleanup_files(list[0])
@@ -420,6 +430,9 @@
       @groupmap[grouporuser]['members'].grep(user)[0]
     end
   end
+  def isuser(user)
+    @usermap[user] && @usermap.keys.grep(user)[0]
+  end
   def isgroup(grp)
     @groupmap[grp]
   end
@@ -436,7 +449,10 @@
   def groupname(grp)
     @groupmap[grp] && @groupmap[grp]['name']
   end
-  def day_all(d, user=nil)
+  def name2group(name)
+    @groupmap.find{|g, v| v.is_a?(Hash) && v['name']==name}
+  end
+  def day_all(d, user=nil, personalonly = nil)
     y, m, d = d.scan(%r,(\d\d\d\d+)/(\d+)/(\d+),)[0]
     #daydir = File.join(@dir, "%04d"%y.to_i, "%02d"%m.to_i, "%02d"%d.to_i)
     daydir = File.join("s", "%04d"%y.to_i, "%02d"%m.to_i, "%02d"%d.to_i)
@@ -455,6 +471,7 @@
 	visible = false
 	#next unless /@/ =~ who	# user must be as user@do.ma.in
 	next unless account_exists(who)
+	next if personalonly && who != user
 	who.untaint
 	dir = File.join(t, who)
 	next unless test(?d, dir) && test(?x, dir)
@@ -628,9 +645,11 @@
     ntl = {}
     return ntl unless test(?d, @crondir)
     Dir.foreach(@crondir){|datedir|
+      next unless /(\d\d\d\d+)-(\d+)-(\d+)-(\d\d\d\d)/ =~ datedir
+      datedir = sprintf("%04d-%02d-%02d-%04d",
+			$1.to_i, $2.to_i, $3.to_i, $4.to_i)
       dd = File.join(@crondir, datedir)
       next unless test(?d, dd)
-      next unless /(\d\d\d\d+)-(\d+)-(\d+)-(\d\d\d\d)/ =~ dd
       y, m, d, hm = $1.to_i, $2.to_i, $3.to_i, $4.to_i
       hh = hm/100 % 60
       mm = (hm%100) % 60
@@ -639,7 +658,14 @@
       #
       # collect them
       Dir.foreach(dd){|user|
-	next unless /@/ =~ user || isgroup(user)
+	# next unless /@/ =~ user || isgroup(user)
+	next if /^\./ =~ user
+	if isgroup(user)
+	  user = @groupmap.keys.grep(user)[0]
+	else
+	  user = @usermap.keys.grep(user)[0]
+	end
+	next unless user
 	ud = File.join(dd, user)
 	next unless test(?d, ud)
 	ntl[user] = {}
@@ -649,6 +675,8 @@
 	    gomifiles << File.join(ud, date)
 	    next
 	  end
+	  date = sprintf("%04d-%02d-%02d-%04d",
+			 $1.to_i, $2.to_i, $3.to_i, $4.to_i)
 	  f = File.join(ud, date)
 	  if test(?s, f)
 	    ntl[user][date] = {}
@@ -688,7 +716,16 @@
   # remove files in FILES, and remove parent directory if possible
   def cleanup_files(files)
     sentinel = File.stat(@dir).ino
+    $0.untaint
+    scriptsuid = File.stat($0).uid
     for f in files
+      if $SAFE > 0
+	f.untaint
+	if test(?e, f) && File.stat(f).uid != scriptsuid
+	  f.taint
+	end
+      end
+      printf "Removing %s\n", f if $DEBUG
       File.unlink(f) if test(?e, f)
       d = f
       loop {
@@ -713,6 +750,9 @@
   def foo=(str)
     @str = str
   end
+  def append(str)
+    @str = str+@str
+  end
   def print(str)
     @str << str
   end
@@ -876,6 +916,7 @@
     @conf = nil
     @schedulearea = {'rows'=>'4', 'cols'=>'60', 'name'=>'schedule'}
     @oldagent = (%r,Mozilla/4, =~ ENV['HTTP_USER_AGENT'])
+    @lang = 0
     @opt = {
       'conf'		=> @mybase+".cf",
       'css'		=> @mybase+".css",
@@ -895,6 +936,7 @@
       'alldaydir'	=> '3000',
       'pswdlen'		=> 4,
       'pswddb'		=> 'a5pswd',
+      'lang'		=> 'j',
     }
     @ntlist = [
       ['nt10m', "10"+msg('minutes', 'before')],
@@ -920,12 +962,13 @@
   def doit()
     @params = getarg()
     @cookie = getcookie()
+    @lang = (/^j/i =~ @opt['lang'] ? 0 : 1)
     p @cookie if $DEBUG
     p @params if $DEBUG
 
-    @O.print @H.contenttype()
-    @O.print @H.head("After 5", @opt['css'])
-    @O.print @H.startelement("body", true)
+    @params['displaymode'] = @params['displaymode'] || @cookie['displaymode']
+    personal = /personal/i =~ @params['displaymode']
+    bodyclass = if personal then {'class'=>'personal'} end
 
     ######### @O.puts @H.p(@cookie.inspect)  #cookie check!
 
@@ -940,8 +983,12 @@
       @params['user'] = @cookie['user']
     end
     @params['user'] = safecopy(@params['user'])
+
     eval @job
-    # @job.call
+    @O.append(@H.contenttype() +
+	      @H.head("After 5"+@job.sub(/\s*/, ' '), @opt['css']))
+    # @job is here
+    @O.print @H.startelement("body", bodyclass, true)
     @O.print @H.endelement(nil, true) # body
     @O.print "</html>\n"	# html
     setcookie()
@@ -978,15 +1025,24 @@
 	'minutes'	=> ['分', 'minutes'],
 	'hours'		=> ['時間', 'hour(s)'],
 	'days'		=> ['日', 'day(s)'],
+	'daystodisplay'	=> ['日分表示', 'days to display'],
 	'before'	=> ['前', 'before'],
+	'precedingday'	=> ['前日', 'Preceding day'],
 	'theday'	=> ['当日朝', "the day's morning"],
 	'night'		=> ['(夜)', '(night)'],
 	'publicok'	=> ['メンバーに<br>見せてもええね?',
 	  'visible from other members?'],
 	'public'	=> ['公', 'pub'],
 	'nonpublic'	=> ['非', 'sec'],
+	'through'	=> ['〜', '=&gt;'],
 	'yes'		=> ['はいな', 'yes'],
 	'no'		=> ['やだ', 'nope'],
+	'wnames'	=> [%w[日 月 火 水 木 金 土],
+	  %w[sun mon tue wed thu fri sat]],
+	'whichday'	=> ['<small>(まとめ登録の場合)</small><br>期間中のどの日に?',
+	  '<small>(On multiple registration)</small><br>Which days in the term?'],
+	'singleday'	=> ['一日分だけ登録', '1day regist'],
+	'everyday'	=> ['毎日', 'everyday'],
 	'invaliddate'	=> ['日付指定が変みたい', 'Invalid time string'],
 	'past'		=> ['それはもう過去の話ね', 'It had Pasted'],
 	'putsomething'	=> ['何か書こうや', 'Write some message please'],
@@ -1034,11 +1090,15 @@
 	'name'		=> ['名前', 'name'],
 	'anystring'	=> ['(日本語OK)', '(any length, any characters)'],
 	'setto'		=> ['を設定 → ', 'set to '],
+	'dupname'	=> ['あー、%sってグループ名は既にあるん素。別のにして.',
+	  "Group name `%s' already exists, choose another name."],
 	'management'	=> ['管理', 'management'],
 	'administrator'	=> ['管理者', 'Administrator'],
 	'newgroup'	=> ['新規グループ作成', 'Create new group'],
 	'adminop'	=> ['管理<br>操作', "Administrative<br>operation"],
 	'member'	=> ['メンバー', 'Member'],
+	'personalmode'	=> ['自分のだけ表示モード', 'Display Personal Only'],
+	'normalmode'	=> ['全員分表示モード', "Display Everyone's"],
 	'addedtogroup'	=> ['をグループに追加 →', 'added to the group:'],
 	'removedfromgp'	=> ['をグループから削除:', 'removed from the group:'],
 	'soleadmin'	=> ['%s は %s の唯一の管理者なのでやめられないのだ',
@@ -1070,22 +1130,20 @@
 	  'Nothing to do for this transaction.']
       }
     end
-    lang=0
     keyword.collect{|k|
       if @msg[k].is_a?(Array)
-	@msg[k][lang]
+	@msg[k][@lang]
       elsif @msg[k].is_a?(String)
 	@msg[k]
       else
 	''
       end
-    }.join(['', ' '][lang])
+    }.join(['', ' '][@lang])
   end
 
   def setcookie()
-    cookievals = %w[user passwd ^nt]
     p = {}
-    @params.keys.grep(/^(user$|passwd$|nt)/){|v|
+    @params.keys.grep(/^(user$|passwd$|display(mode|days)$|nt)/){|v|
       p[v] = @params[v].to_s.strip
     }
     c = gencookie(p, 3600*6*1)
@@ -1343,7 +1401,12 @@
     @H.element("p"){
       me = @myname+"?-"; delim = " / "
       @H.a(me+'userman', msg('user', 'management')) + delim + \
-      @H.a(me+'groupman', msg('group', 'management'))
+      @H.a(me+'groupman', msg('group', 'management')) + delim + \
+      if /personal/i =~ @params['displaymode']
+	@H.a(me+'today_n', msg('normalmode'))
+      else
+	@H.a(me+'today_p', msg('personalmode'))
+      end
     }
   end
 
@@ -1384,10 +1447,15 @@
     todayd = today.day
     tdclass = {}
     tdclass["width"] = "64px" if @oldagent # workaround for NN4
+    personal = /personal/ =~ @params['displaymode']
 
     holiday = Holiday.new
     # create dayofweek header
     @O.print @H.elementln("h1", nil){sprintf "%d/%d", day.year, day.month}
+    # which mode?
+    @O.print @H.p(msg(personal ? 'personalmode' : 'normalmode'))
+    #
+    # display table
     @O.print @H.startelement("table", {'border'=>"1", 'class'=>'main'})
 
     # day of week
@@ -1411,20 +1479,24 @@
 	    if d>0 && d <= last
 	      date = "%d/%d/%d"%[day.year, day.month, d]
 	      @H.element("p", {'class'=>todayp ? 'todayline' : 'dayline'}){
-		@H.a(@myname+"?-show+"+date, d.to_s)
+		@H.a(@myname+"?-show+"+date, "%4d"%d)
 	      } + \
 	      # isHoliday?
 	      if hd
 		@H.element("small"){hd.join("<br>")}
 	      end.to_s + \
 	      @H.element("p", {'class'=>'topic'}){
-		s = @sc.day_all(date, @params['user'])
+		s = @sc.day_all(date, @params['user'], personal)
 		if !s.empty?
 		  s.keys.sort.collect{|time|
 		    s[time].keys.sort.collect{|who|
 		      sprintf "%04s:%s",
 			time == @opt['alldaydir'] ? msg('allday') : time,
-			nickname(who)
+			if personal
+			  s[time][who]['sched'].split("\n")[0]
+			else
+			  nickname(who)
+			end
 		    }.join
 		  }.join("<br>")
 		else
@@ -1494,7 +1566,7 @@
   #
   # Put carrying values
   def hiddenvalues()
-    h = %w[user].collect{|v|
+    h = %w[user displaymode].collect{|v|
       if @params[v]
 	sprintf "<input type=\"hidden\" name=\"%s\" value=\"%s\">\n",
 	  v, @params[v]
@@ -1503,132 +1575,185 @@
     h.delete(nil)
     h.join
   end
+  def date2ymd(date)
+    %r,(\d\d\d\d+)/(\d\d?)/(\d\d?), =~ date and
+      [$1.to_i, $2.to_i, $3.to_i]
+  end
   #
   # Return the string of table
-  def dayTableString(s, user, date)
+  def dayTableString(user, datestr, range, personal = nil)
+    #s = @sc.day_all(date, user, personal)
+    #return '' if s.empty?
     r = ''
-    r << @H.startelement("table", {'border'=>'1'}, true)
-    r << @H.element("tr", nil){
-      @H.element("th", {'class'=>'time'}){'TIME'} + \
-      @H.element("th", nil){'Schedule'}
-    }
-    for time in s.keys
-      tstr = case time
-	     when @opt['alldaydir']
-	       msg('allday')
-	     else
-	       sprintf "%02d:%02d", time.to_i/100, time.to_i%100
-	     end
-      r << @H.startelement("tr", nil, true)
-      r << @H.element("th", {'class'=>'time'}){tstr}
-      r << @H.element("td"){
-	@H.elementln("table"){
-	  s[time].keys.collect{|who|
-	    editable = (user==who || @sc.ismember(user, who))
-	    groupp   = grepgroup(who)
-	    @H.element("tr"){
-	      @H.element("td", {'class'=>groupp ? 'group' : 'who'}){
-		if !groupp && webpage(who)
-		  @H.a(webpage(who), nickname(who))
-		else
-		  nickname(who)
-		end
-	      } + \
-	      @H.element("td"){
-		if editable
-		  s[time][who]['pub'] ? msg('public') :
-		    msg('nonpublic')
-		else
-		  @opt['tdskip']
-		end
-	      } + \
-	      @H.element("td"){
-		if editable
-		  @H.a(@myname+"?-modify+#{date}/#{time}/#{who}",
-		       msg('modify'))
-		else
-		  @opt['tdskip']
-		end
-	      } + \
-	      @H.element("td"){
-		if editable
-		  @H.a(@myname+"?-remove+#{date}/#{time}/#{who}",
-		       msg('remove'))
-		else
-		  @opt['tdskip']
-		end
-	      } + \
-	      @H.element("td"){s[time][who]['sched']}
-	    }
-	  }.join("\n")
+    header = @H.startelement("table", {'border'=>'1'}, true)
+
+    day = Time.mktime(*date2ymd(datestr))
+    i = -1
+    while (i+=1) < range
+      d = Time.at(day+i*3600*24)
+      date = sprintf("%04d/%02d/%02d", d.year, d.month, d.day)
+      s = @sc.day_all(date, user, personal)
+      next if s.empty?
+
+      r << @H.element("tr", nil){
+	@H.element("th", {'class'=>'time'}){'TIME'} + \
+	@H.element("th", nil){date+' - Schedule'}
+      }
+      for time in s.keys
+	tstr = case time
+	       when @opt['alldaydir']
+		 msg('allday')
+	       else
+		 sprintf "%02d:%02d", time.to_i/100, time.to_i%100
+	       end
+	r << @H.startelement("tr", nil, true)
+	r << @H.element("th", {'class'=>'time'}){tstr}
+	r << @H.element("td"){
+	  @H.elementln("table"){
+	    s[time].keys.collect{|who|
+	      editable = (user==who || @sc.ismember(user, who))
+	      groupp   = grepgroup(who)
+	      @H.element("tr"){
+		@H.element("td", {'class'=>groupp ? 'group' : 'who'}){
+		  if !groupp && webpage(who)
+		    @H.a(webpage(who), nickname(who))
+		  else
+		    nickname(who)
+		  end
+		} + \
+		@H.element("td"){
+		  if editable
+		    s[time][who]['pub'] ? msg('public') :
+		      msg('nonpublic')
+		  else
+		    @opt['tdskip']
+		  end
+		} + \
+		@H.element("td"){
+		  if editable
+		    @H.a(@myname+"?-modify+#{date}/#{time}/#{who}",
+			 msg('modify'))
+		  else
+		    @opt['tdskip']
+		  end
+		} + \
+		@H.element("td"){
+		  if editable
+		    @H.a(@myname+"?-remove+#{date}/#{time}/#{who}",
+			 msg('remove'))
+		  else
+		    @opt['tdskip']
+		  end
+		} + \
+		@H.element("td"){s[time][who]['sched']}
+	      }
+	    }.join("\n")
+	  }
 	}
-      }
-      r << @H.endelement()
+	r << @H.endelement()
+      end
     end
-    r << @H.endelement()
-    r
+    footer = @H.endelement()
+    if r > ''
+      header + r + footer
+    else
+      ''
+    end
   end
   #
-  # show the schedule list of specified date
-  #
-  def show(date)
-    if !checkauth
-      return nil
-    end
-    border1 = {'border'=>'1'}
-    user = safecopy(@params['user'])
-    s = @sc.day_all(date, user)
-    mygroup = @sc.groups().select{|g|@sc.ismember(user, g)}
-
-    @O.print @H.element("h1", nil){
-      sprintf msg('fmtdaysschedule'), date
-    }
-    if s.empty?
-      @O.print @H.p(msg('noplan'))
-    else
-      @O.print dayTableString(s, user, date)
-    end #is_empty?
-    thisyear, thismonth, thisday = date.scan(%r,(\d\d\d\d+)/(\d+)/(\d+),)[0]
-    mstr = sprintf "%04d/%02d", thisyear.to_i, thismonth.to_i
-    @O.print @H.a(@myname+"?-month+"+mstr,
-		  sprintf(msg('tomonthlist'), mstr))
+  # new form
+  def displayRegistForm(date, multiple = true)
     #
     # Link button to add new plan
     #now = Time.now+3600*24
+    thisyear, thismonth, thisday = date.scan(%r,(\d\d\d\d+)/(\d+)/(\d+),)[0]
+    user = @params['user']
     now = Time.mktime(thisyear, thismonth, thisday.to_i, Time.now.hour)
     y, m, d, h, min = now.year, now.month, now.day, now.hour, now.min
+    nextweek = Time.at(now+3600*24*7)
+    ey, em, ed = nextweek.year, nextweek.month, nextweek.day
+    rcsp = (multiple ? {'colspan'=>'2'} : nil)
+    wnames = @msg['wnames'][@lang]
+    wnames << @msg['everyday'][@lang]
+
     @O.print @H.element('h2', nil, true){msg('addsched')}
     @O.print @H.element('p', nil){msg('defthisday')}
+
     @O.print @H.element("form", {'action'=>@myname+"?-addsched", 'method'=>'POST'}){
+      border1 = {'border'=>'1'}
+      border1c = {'border'=>'1', 'class'=>'c'}
+      mygroup = @sc.groups().select{|g|@sc.ismember(user, g)}
       @H.elementln('table', border1){
 	@H.elementln('tr'){
 	  @H.element('th'){'Name'} + \
-	  @H.element('td'){
+	  @H.element('td', rcsp){
 	    hiddenvalues() + @sc.nickname(user)
 	  }
 	} + \
 	@H.elementln('tr'){
 	  @H.element('th'){'Year'} + \
-	  @H.element('td'){@H.select_integer("year", y, y+5, y)}
+	  @H.element('td'){@H.select("year", y..y+5, y)} + \
+	  if multiple
+	    @H.element('td'){
+	      d1 = msg('singleday')
+	      msg('through')+@H.select("endyear", [d1]+(y..y+5).to_a, d1)
+	    }
+	  end
 	} + \
 	@H.elementln('tr'){
 	  @H.element('th'){'Month'} + \
-	  @H.element('td'){@H.select_integer("month", 1, 12, m)}
+	  @H.element('td'){@H.select("month", 1..12, m)} + \
+	  if multiple
+	    @H.element('td'){
+	      msg('through')+@H.select("endmonth", 1..12, em)
+	    }
+	  end
 	} + \
 	@H.elementln('tr'){
 	  @H.element('th'){'Day'} + \
-	  @H.element('td'){@H.select_integer("day", 1, 31, d)}
+	  @H.element('td'){@H.select("day", 1..31, d)} + \
+	  if multiple
+	    @H.element('td'){
+	      msg('through')+@H.select("endday", 1..31, ed)
+	    }
+	  end
 	} + \
+	if multiple
+	  @H.elementln('tr'){
+	    @H.element('th'){
+	      msg('whichday')
+	    } + \
+	    @H.element('td', rcsp){
+	      @H.elementln('table', border1c){
+		@H.element('tr'){
+		  i=-1
+		  wnames.collect{|w|
+		    @H.element('td'){
+		      i+=1
+		      @H.radio('whichday', i.to_s, '', i==wnames.length-1)
+		    }
+		  }.join("\n")
+		} + \
+		@H.element('tr'){
+		  i=-1
+		  wnames.collect{|w|
+		    @H.element('td'){w}
+		  }.join
+		}
+	      }
+	    }
+	  }
+	end + \
 	@H.elementln('tr'){
 	  @H.element('th'){'Time<br>'+ \
 	    sprintf(msg('24hour'), @opt['alldaydir'])} + \
-	  @H.element('td'){
+	  @H.element('td', rcsp){
 	    '<input type=text name="time" value="3000" size=8 maxlength="4">'
 	  }
 	} + \
 	@H.elementln('tr'){
 	  @H.element('th'){msg('publicok')} + \
-	  @H.element('td'){
+	  @H.element('td', rcsp){
 	    @H.radio('pub', 'yes', msg('yes')+'<br>', true) + \
 	    @H.radio('pub', 'no', msg('no'))
 	  }
@@ -1652,9 +1777,53 @@
 	  @H.radio('registas', 'no', msg('personal'))
 	}
       end.to_s + "\n" + \
+      @H.radio('editmode', 'remove', 'Delete?') + " / " + \
+      @H.radio('editmode', 'modify', 'Overwrite?') + " / " + \
+      @H.radio('editmode', 'append', 'Append?', true) + "<br>\n" + \
       @H.element("textarea", @schedulearea){} + "<br>\n" + \
       @H.submit_reset("GO")
     } #form
+  end
+  #
+  # show the schedule list of specified date
+  #
+  def show(date)
+    if !checkauth
+      return nil
+    end
+    user = safecopy(@params['user'])
+    personal = (/personal/i =~ @params['displaymode'])
+    @params['displaydays'] = @params['displaydays'] || @cookie['displaydays']
+    days = (@params['displaydays'] || 1).to_i
+
+    # str = @sc.day_all(date, user, personal)
+    outstr = dayTableString(user, date, days, personal)
+
+    @O.print @H.element("h1", nil){
+      sprintf msg('fmtdaysschedule'), date
+    }
+    ## @O.print @H.p()
+    @O.print @H.elementln("form", {'action'=>@myname+"?-show+#{date}", 'method'=>'POST'}){
+      @H.elementln("p"){
+	msg(personal ? 'personalmode' : 'normalmode') + " " + \
+	@H.select("displaydays", 1..30, days) + msg('daystodisplay') + \
+	@H.submit("GO", "GO")
+      }
+    }
+    if outstr > ''
+      @O.print outstr
+    else
+      @O.print @H.p(msg('noplan'))
+    end #is_empty?
+    thisyear, thismonth, thisday = date.scan(%r,(\d\d\d\d+)/(\d+)/(\d+),)[0]
+    mstr = sprintf "%04d/%02d", thisyear.to_i, thismonth.to_i
+    @O.print @H.a(@myname+"?-month+"+mstr,
+		  sprintf(msg('tomonthlist'), mstr))
+
+
+    #
+    # Display registration form
+    displayRegistForm(date)
     @O.print "show" if user == @author
   end
 
@@ -1731,6 +1900,11 @@
   def cancel_notify(user, year, month, day, time)
     reg_notify(user, year, month, day, time, 'dummy', true)
   end
+
+  def commit_schedule(who, y, m, d, timedir, text, repl, pub)
+
+  end
+
   #
   # add or remove a schedule
   #
@@ -1744,7 +1918,7 @@
       registerer = as
     end
     now = Time.now
-    y, m, d, h, min = now.year, now.month, now.day, now.hour, now.min
+    #y, m, d, h, min = now.year, now.month, now.day, now.hour, now.min
 
     $KCODE='e' if $DEBUG
     @O.print @params.inspect if $DEBUG
@@ -1773,53 +1947,84 @@
 	@params['year'], @params['month'], @params['day'], @params['time']
       return nil
     end
-    unless @params['schedule'] && @params['schedule'].strip > ''
+
+    #
+    # Check continuous schedule registration
+    wwday = @params['whichday'].to_i
+    if @params['endyear'] && @params['endmonth'] && @params['endday'] &&
+	(ey=@params['endyear'].to_i) > 0 &&
+	(em=@params['endmonth'].to_i) > 0 &&
+	(ed=@params['endday'].to_i) > 0
+      daylist = []
+      endtime = Time.mktime(ey, em, ed, 23, 59)
+      ti = time
+      begin
+	if wwday==7 || wwday==ti.wday
+	  daylist << [ti.year, ti.month, ti.day]
+	end
+      end while (ti=Time.at(ti+3600*24)) < endtime
+    else
+      daylist = [[sy, sm, sd]]
+    end
+
+    if !remove && !(@params['schedule'] && @params['schedule'].strip > '')
       outputError msg('putsomething')
       return nil
     end
 
-    # do remove or addition
-    if remove
-      cancel_notify(registerer, sy, sm, sd, timedir)
-      begin
-	@sc.remove(registerer, sy, sm, sd, timedir)
-	#########@O.print @H.p(msg('remove')+msg('done'))
-      rescue
-	outputError("Failed"+$!)
-      end
-    else
-      if time < now
-	outputError(msg('past'))
-	return nil
+
+    for y, m, d in daylist
+      # do remove or addition
+      if remove
+	cancel_notify(registerer, y, m, d, timedir)
+	begin
+	  @sc.remove(registerer, y, m, d, timedir)
+	  #########@O.print @H.p(msg('remove')+msg('done'))
+	rescue
+	  outputError("Failed"+$!)
+	end
+      else
+	if time < now
+	  outputError(msg('past'))
+	  return nil
+	end
+	begin
+	  (text = @params['schedule'].strip.gsub(/\r+\n/, $/)) << "\n"
+	  replace = (/modify/i =~ @params['editmode'])
+	  rc = @sc.register(registerer, y, m, d, timedir, text, replace)
+	  if @params['pub'] && /yes/ =~ @params['pub']
+	    @sc.putfile(registerer, y, m, d, timedir, 'pub', "1\n")
+	  else
+	    @sc.removefile(registerer, y, m, d, timedir, 'pub')
+	  end
+	  ########  @O.print @H.p(msg('appended')) if rc == 1
+	rescue
+	  outputError("Failed"+$!)
+	end
+	text = @sc.getschedule(registerer, y, m, d, timedir)
+	reg_notify(registerer, y, m, d, timedir, text)
+
       end
-      begin
-	(text = @params['schedule'].strip.gsub(/\r+\n/, $/)) << "\n"
-	replace = (/modify/i =~ @params['editmode'])
-	rc = @sc.register(registerer, sy, sm, sd, timedir, text, replace)
-	if @params['pub'] && /yes/ =~ @params['pub']
-	  @sc.putfile(registerer, sy, sm, sd, timedir, 'pub', "1\n")
-	else
-	  @sc.removefile(registerer, sy, sm, sd, timedir, 'pub')
-	end
-	########  @O.print @H.p(msg('appended')) if rc == 1
-      rescue
-	outputError("Failed"+$!)
-      end
-      text = @sc.getschedule(registerer, sy, sm, sd, timedir)
-      reg_notify(registerer, sy, sm, sd, timedir, text)
-      if @params['rightnow'] && /yes/i =~ @params['rightnow']
-	header = sprintf("%s/%s/%s %s %s\n%s%s%s\n%s\n",
-			 sy, sm, sd, tmstr, msg('immediatenote'),
-			 msg('registerer_is'), nickname(registerer),
-			 if user!=registerer
-			   sprintf(" (%s%s)",
-				   msg('registerer'), nickname(user))
-			 else
-			   ""
-			 end,
-			 "-"*70)
-	sendnotify(registerer, "Registration completed", header+text)
-      end
+
+    end
+
+    if !remove && @params['rightnow'] && /yes/i =~ @params['rightnow']
+      header = sprintf("%s\n%s/%s/%s%s %s %s\n%s%s%s\n%s\n",
+		       @opt['url'],
+		       sy, sm, sd,
+		       if daylist.length > 1
+			 "-%s/%s/%s" % daylist[-1]
+		       end,
+		       tmstr, msg('immediatenote'),
+		       msg('registerer_is'), nickname(registerer),
+		       if user!=registerer
+			 sprintf(" (%s%s)",
+				 msg('registerer'), nickname(user))
+		       else
+			 ""
+		       end,
+		       "-"*70)
+      sendnotify(registerer, "Registration completed", header+text)
     end
     show(sprintf("%04d/%02d/%02d", sy, sm, sd))
     @O.print "add_remove" if user == @author
@@ -1925,10 +2130,10 @@
 	y, m, d, t = $1.to_i, $2.to_i, $3.to_i, $4.to_i
 	if t > 2359
 	  hhmm = msg('allday')
-	  comment = msg('theday')
+	  comment = msg(now.hour > 18 ? 'precedingday' : 'theday')
 	else
 	  hhmm = sprintf "%02d:%02d", t/100, t%100
-	  diff = Time.mktime(y, m, d, t/100, 5%100) - now
+	  diff = Time.mktime(y, m, d, t/100, t%100) - now
 	  if diff < 7200
 	    comment = "%d%s" % [diff/60, msg('minutes', 'before')]
 	  elsif (ddiff=(Time.mktime(y, m, d)-Time.mktime(now.year, now.month, now.day))/3600/24) == 0
@@ -2172,6 +2377,11 @@
       @H.submit_reset("GO")
     } # form
   end
+  def groupnamesString()
+    @H.elementln("p", {'class'=>'listup'}){
+	@sc.groups().collect{|g|@sc.groupname(g)}.join(", ")
+    }
+  end
   def groupmod()
     if !checkauth
       return nil
@@ -2202,11 +2412,18 @@
       if @sc.isadmin(user, grp) &&
 	  (newname = @params["groupname-#{grp}"]) &&
 	  @sc.groupname(grp) != newname
-	@sc.setgroupname(grp, newname)
-	@O.print @H.elementln("p"){
-	  sprintf "%s %s%s %s",
-	    msg('group'), grp, msg('of', 'name', 'setto'), newname
-	}
+@O.printf "@sc.name2group=%s<br>\n", @sc.name2group(newname)
+	if dupl=@sc.name2group(newname)
+	  @O.print @H.p(sprintf(msg('dupname'), newname))
+	  @O.print groupnamesString()
+
+	else
+	  @sc.setgroupname(grp, newname)
+	  @O.print @H.elementln("p"){
+	    sprintf "%s %s%s %s",
+	      msg('group'), grp, msg('of', 'name', 'setto'), newname
+	  }
+	end
       end
     end
   end
@@ -2342,10 +2559,10 @@
     } # form
     @O.print @H.p(@H.element("span", warnclass){"(*)"}+
 		  msg('recursewarn')) if warnp
-    if group
+    if group && (members = @sc.members(group))[0]
       @O.print @H.p(sprintf(msg('wholemembers'), group))
       @O.print @H.elementln("p", {'class'=>'listup'}){
-	@sc.members(group).collect{|u|@sc.nickname(u)}.join(", ")}
+	members.collect{|u|@sc.nickname(u)}.join(", ")}
     end
 
     #
@@ -2481,6 +2698,8 @@
     }
     somethingdone = nil
     for u in users()
+      u = @sc.isuser(u)		# users() value is considered tainted.
+      next unless u		# Use registered value in @sc.
       for var, kind in {
 	  "mem"=>['members', 'member'], 'adm'=>['admin', 'administrator']}
 	memv = "#{var}-#{u}"
@@ -2541,6 +2760,11 @@
       @O.print @H.p(msg('existent')+newgroup)
       return nil
     end
+    if dupl=@sc.name2group(newgname)
+      @O.print @H.p(sprintf(msg('dupname'), newgname))
+      @O.print groupnamesString()
+      return nil
+    end
     @sc.creategroup(newgroup, newgname, [user])
     admgroup(newgroup)
   end
@@ -2647,6 +2871,12 @@
 	@job = "addsched"
       when "-today"
 	@job = "today"
+      when "-today_p"
+	argument['displaymode'] = 'personal'
+	@job = "today"
+      when "-today_n"
+	argument['displaymode'] = 'normal'
+	@job = "today"
       when "-remove"
 	ARGV.shift
 	@job = 'remove "'+parsedate(ARGV[0])+'"'

yatex.org