############################################################################ # Copyright 2009-2019 Benjamin Kellermann # # # # This file is part of Dudle. # # # # Dudle is free software: you can redistribute it and/or modify it under # # the terms of the GNU Affero General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # Dudle is distributed in the hope that it will be useful, but WITHOUT ANY # # WARRANTY; without even the implied warranty of MERCHANTABILITY or # # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public # # License for more details. # # # # You should have received a copy of the GNU Affero General Public License # # along with dudle. If not, see . # ############################################################################ # BUGFIX for Time.parse, which handles the zone indeterministically class << Time alias_method :old_parse, :parse def Time.parse(date, now=self.now) Time.old_parse("2009-10-25 00:30") Time.old_parse(date) end end require_relative "timestring" class TimePollHead def initialize @data = [] end def col_size @data.size end # returns a sorted array of all columns # column should be the internal representation # column.to_s should deliver humanreadable form def columns @data.sort.collect{|day| day.to_s} end def concrete_times h = {} @data.each{|ds| h[ds.time_to_s] = true } h.keys end def date_included?(date) ret = false @data.each{|ds| ret = ret || ds.date == date } ret end # deletes one concrete column # adds the empty day, if it was the last concrete time of the day # returns true if deletion successful def delete_concrete_column(col) ret = @data.delete(col) != nil @data << TimeString.new(col.date,nil) unless date_included?(col.date) ret end # column is in human readable form # returns true if deletion successful def delete_column(column) if $cgi.include?("togglealloff") # delete one time head_count("%Y-%m-%d",false).each{|day,num| delete_concrete_column(TimeString.new(day,column)) } return true end col = TimeString.from_s(column) if col.time return delete_concrete_column(col) else # delete all concrete times on the given day deldata = [] @data.each{|ts| deldata << ts if ts.date == col.date } deldata.each{|ts| @data.delete(ts) } return !deldata.empty? end end def parsecolumntitle(title) if $cgi.include?("add_remove_column_day") parsed_date = YAML::load(Time.parse("#{$cgi["add_remove_column_month"]}-#{$cgi["add_remove_column_day"]} #{title}").to_yaml) else earlytime = @head.keys.collect{|t|t.strftime("%H:%M")}.sort[0] parsed_date = YAML::load(Time.parse("#{$cgi["add_remove_column_month"]}-#{title} #{earlytime}").to_yaml) end parsed_date end # returns parsed title or nil in case of column not changed def edit_column(column, newtitle, cgi) if cgi.include?("toggleallon") head_count("%Y-%m-%d",false).each{|day,num| parsed_date = TimeString.new(day,newtitle) delete_column(day) if @data.include?(TimeString.new(day,nil)) @data << parsed_date unless @data.include?(parsed_date) } return newtitle end if cgi.include?("columntime") && cgi["columntime"] == "" @edit_column_error = _("To add a time other than the default times, please enter some string here (e. g., 09:30, morning, afternoon).") return nil end delete_column(column) if column != "" parsed_date = TimeString.new(newtitle, cgi["columntime"] != "" ? cgi["columntime"] : nil) if @data.include?(parsed_date) @edit_column_error = _("This time has already been selected.") return nil else @data << parsed_date parsed_date.to_s end end # returns a sorted array, containing the big units and how often each small is in the big one # small and big must be formatted for strftime # ex: head_count("%Y-%m") returns an array like [["2009-03",2],["2009-04",3]] # if notime = true, the time field is stripped out before counting def head_count(elem, notime) data = @data.collect{|day| day.date} data.uniq! if notime ret = Hash.new(0) data.each{|day| ret[day.strftime(elem)] += 1 } ret.sort end def to_html(scols,config = false,activecolumn = nil) ret = "" head_count("%Y-%m",false).each{|title,count| year, month = title.split("-").collect{|e| e.to_i} ret += "#{Date.parse("#{year}-#{month}-01").strftime("%b %Y")}\n" } ret += "" head_count("%Y-%m-%d",false).each{|title,count| ret += "#{Date.parse(title).strftime('%a, %d')}\n" } def sortsymb(scols,col) return < #{scols.include?(col) ? SORT : NOSORT} SORTSYMBOL end ret += "" + _("Name") + " #{sortsymb(scols,"name")}" @data.sort.each{|date| ret += "#{CGI.escapeHTML(date.time_to_s)} #{sortsymb(scols,date.to_s)}\n" } ret += "" + _("Last edit") + " #{sortsymb(scols,"timestamp")}\n\n" ret end def datenavi val,revision case val when MONTHBACK navimonth = Date.parse("#{@startdate.strftime('%Y-%m')}-1")-1 when MONTHFORWARD navimonth = Date.parse("#{@startdate.strftime('%Y-%m')}-1")+31 else raise "Unknown navi value #{val}" end return <
END end def timenavi val,revision case val when EARLIER return "" if @firsttime == 0 str = EARLIER + " " + _("Earlier") firsttime = [@firsttime-2,0].max lasttime = @lasttime when LATER return "" if @lasttime == 23 str = LATER + " " + _("Later") firsttime = @firsttime lasttime = [@lasttime+2,23].min else raise "Unknown navi value #{val}" end return <
END end def edit_column_htmlform(activecolumn, revision) # calculate start date, first and last time to show if $cgi.include?("add_remove_column_month") @startdate = Date.parse("#{$cgi["add_remove_column_month"]}-1") else @startdate = Date.parse("#{Date.today.year}-#{Date.today.month}-1") end times = concrete_times times.delete("") # do not display empty cell in edit-column-form realtimes = times.collect{|t| begin Time.parse(t) if t =~ /^\d\d:\d\d$/ rescue ArgumentError end }.compact [9,16].each{|i| realtimes << Time.parse("#{i.to_s.rjust(2,"0")}:00")} ["firsttime","lasttime"].each{|t| realtimes << Time.parse($cgi[t]) if $cgi.include?(t) } @firsttime = realtimes.min.strftime("%H").to_i @lasttime = realtimes.max.strftime("%H").to_i def add_remove_button(klasse, buttonlabel, action, columnstring, revision, pretext = "") titlestr = _("Add column") titlestr = _("Delete column") if klasse == "chosen" || klasse == "delete" return <
#{pretext}
FORM end hintstr = _("Click on the dates to add or remove columns.") ret = <
#{hintstr}
END ret += datenavi(MONTHBACK,revision) ret += "" ret += datenavi(MONTHFORWARD,revision) ret += "\n" 7.times{|i| # 2010-03-01 was a Monday, so we can use this month for a dirty hack ret += "" } ret += "\n" ((@startdate.wday+7-1)%7).times{ ret += "" } d = @startdate while true do klasse = "notchosen" varname = "new_columnname" klasse = "disabled" if d < Date.today if date_included?(d) klasse = "chosen" varname = "deletecolumn" end ret += "" d = d.next break if d.month != @startdate.month ret += "\n" if d.wday == 1 end ret += <
#{@startdate.strftime('%b %Y')}
#{Date.parse("2010-03-0#{i+1}").strftime("%a")}
#{add_remove_button(klasse, d.day, varname, d.strftime('%Y-%m-%d'),revision)}
END ########################### # starting hour input ########################### ret += "" if col_size > 0 optstr = _("Optional:") hintstr = _("Select specific start times.") ret += < #{optstr}
#{hintstr} END ret += "" head_count("%Y-%m",true).each{|title,count| year,month = title.split("-").collect{|e| e.to_i} ret += "\n" } ret += "" head_count("%Y-%m-%d",true).each{|title,count| coltime = Date.parse(title) ret += "" } ret += "" days = @data.sort.collect{|date| date.date }.uniq chosenstr = { "chosen" => _("Selected"), "notchosen" => _("Not selected"), "disabled" => _("Past") } ret += timenavi(EARLIER,revision) (@firsttime..@lasttime).each{|i| times << "#{i.to_s.rjust(2,"0")}:00" } times.flatten.compact.uniq.sort{|a,b| if a =~ /^\d\d:\d\d$/ && !(b =~ /^\d\d:\d\d$/) -1 elsif !(a =~ /^\d\d:\d\d$/) && b =~ /^\d\d:\d\d$/ 1 else a.to_i == b.to_i ? a <=> b : a.to_i <=> b.to_i end }.each{|time| ret += < END days.each{|day| timestamp = TimeString.new(day,time) klasse = "notchosen" klasse = "disabled" if timestamp < TimeString.now if @data.include?(timestamp) klasse = "chosen" hiddenvars = "" else hiddenvars = "" if @data.include?(TimeString.new(day,nil)) # change day instead of removing it if no specific hour exists for this day hiddenvars += "" end end ret += "" } ret += "\n" } ret += timenavi(LATER,revision) ret += "" days.each{|d| ret += <
END if @data.include?(TimeString.new(d,nil)) ret += "" end addstr = _("Add") hintstr = _("e. g., 09:30, morning, afternoon") ret += <
END } ret += "
#{Date.parse("#{year}-#{month}-01").strftime("%b %Y")}
" + _("Time") + "" + add_remove_button("delete",DELETE, "deletecolumn", coltime.strftime("%Y-%m-%d"), revision, "#{coltime.strftime('%a, %d')} ") + "
" + add_remove_button(klasse, chosenstr[klasse], "columntime", CGI.escapeHTML(timestamp.time_to_s), revision, hiddenvars) + "
" ret += "
#{@edit_column_error}
" if @edit_column_error end ret += "" ret end end