2016-10-19 03:11:36 +00:00
|
|
|
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
2015-09-09 06:52:05 +00:00
|
|
|
|
|
|
|
class Calendar < ApplicationModel
|
2017-05-02 15:21:13 +00:00
|
|
|
include ChecksClientNotification
|
|
|
|
include CanUniqName
|
2017-01-31 17:13:45 +00:00
|
|
|
|
2015-09-09 06:52:05 +00:00
|
|
|
store :business_hours
|
|
|
|
store :public_holidays
|
|
|
|
|
2015-09-25 07:21:55 +00:00
|
|
|
before_create :validate_public_holidays, :fetch_ical
|
|
|
|
before_update :validate_public_holidays, :fetch_ical
|
2015-09-10 19:09:50 +00:00
|
|
|
after_create :sync_default, :min_one_check
|
|
|
|
after_update :sync_default, :min_one_check
|
|
|
|
after_destroy :min_one_check
|
|
|
|
|
2015-09-09 06:52:05 +00:00
|
|
|
=begin
|
|
|
|
|
2015-09-22 14:48:43 +00:00
|
|
|
set inital default calendar
|
|
|
|
|
|
|
|
calendar = Calendar.init_setup
|
|
|
|
|
|
|
|
returns calendar object
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def self.init_setup(ip = nil)
|
|
|
|
|
2015-09-29 08:23:13 +00:00
|
|
|
# ignore client ip if not public ip
|
|
|
|
if ip && ip =~ /^(::1|127\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.|192\.168\.)/
|
|
|
|
ip = nil
|
|
|
|
end
|
|
|
|
|
2016-01-13 13:12:05 +00:00
|
|
|
# prevent multible setups for same ip
|
|
|
|
cache = Cache.get('Calendar.init_setup.done')
|
|
|
|
return if cache && cache[:ip] == ip
|
|
|
|
Cache.write('Calendar.init_setup.done', { ip: ip }, { expires_in: 1.hour })
|
|
|
|
|
2015-09-22 14:48:43 +00:00
|
|
|
# call for calendar suggestion
|
|
|
|
calendar_details = Service::GeoCalendar.location(ip)
|
|
|
|
return if !calendar_details
|
|
|
|
|
2017-01-31 17:13:45 +00:00
|
|
|
calendar_details['name'] = Calendar.generate_uniq_name(calendar_details['name'])
|
2015-09-22 14:48:43 +00:00
|
|
|
calendar_details['default'] = true
|
|
|
|
calendar_details['created_by_id'] = 1
|
|
|
|
calendar_details['updated_by_id'] = 1
|
|
|
|
|
|
|
|
# find if auto generated calendar exists
|
|
|
|
calendar = Calendar.find_by(default: true, updated_by_id: 1, created_by_id: 1)
|
|
|
|
if calendar
|
2017-09-11 11:16:08 +00:00
|
|
|
calendar.update!(calendar_details)
|
2015-09-22 14:48:43 +00:00
|
|
|
return calendar
|
|
|
|
end
|
|
|
|
create(calendar_details)
|
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
2015-09-09 06:52:05 +00:00
|
|
|
get default calendar
|
|
|
|
|
|
|
|
calendar = Calendar.default
|
|
|
|
|
|
|
|
returns calendar object
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def self.default
|
|
|
|
find_by(default: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
2016-12-13 13:58:13 +00:00
|
|
|
returns preset of ical feeds
|
2015-09-09 06:52:05 +00:00
|
|
|
|
|
|
|
feeds = Calendar.ical_feeds
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
{
|
2016-12-02 11:24:00 +00:00
|
|
|
'http://www.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics' => 'US',
|
2015-09-09 06:52:05 +00:00
|
|
|
...
|
|
|
|
}
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def self.ical_feeds
|
2017-11-23 08:09:44 +00:00
|
|
|
data = YAML.load_file(Rails.root.join('config', 'holiday_calendars.yml'))
|
2016-12-02 11:24:00 +00:00
|
|
|
url = data['url']
|
|
|
|
|
|
|
|
data['countries'].map do |country, domain|
|
2017-11-23 08:09:44 +00:00
|
|
|
[format(url, domain: domain), country]
|
2016-12-02 11:24:00 +00:00
|
|
|
end.to_h
|
2015-09-09 06:52:05 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
|
|
|
get list of available timezones and UTC offsets
|
|
|
|
|
|
|
|
list = Calendar.timezones
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
{
|
|
|
|
'America/Los_Angeles' => -7
|
|
|
|
...
|
|
|
|
}
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def self.timezones
|
|
|
|
list = {}
|
2017-10-01 12:25:52 +00:00
|
|
|
TZInfo::Timezone.all_country_zone_identifiers.each do |timezone|
|
2015-09-09 06:52:05 +00:00
|
|
|
t = TZInfo::Timezone.get(timezone)
|
|
|
|
diff = t.current_period.utc_total_offset / 60 / 60
|
|
|
|
list[ timezone ] = diff
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2015-09-09 06:52:05 +00:00
|
|
|
list
|
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
|
|
|
syn all calendars with ical feeds
|
|
|
|
|
|
|
|
success = Calendar.sync
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
true # or false
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def self.sync
|
2016-12-02 11:24:00 +00:00
|
|
|
Calendar.find_each(&:sync)
|
2015-09-09 06:52:05 +00:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
|
|
|
syn one calendars with ical feed
|
|
|
|
|
|
|
|
calendar = Calendar.find(4711)
|
|
|
|
success = calendar.sync
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
true # or false
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2015-09-23 07:10:07 +00:00
|
|
|
def sync(without_save = nil)
|
2015-09-09 06:52:05 +00:00
|
|
|
return if !ical_url
|
2016-11-10 15:11:59 +00:00
|
|
|
|
|
|
|
# only sync every 5 days
|
2017-08-24 16:59:53 +00:00
|
|
|
if id
|
|
|
|
cache_key = "CalendarIcal::#{id}"
|
|
|
|
cache = Cache.get(cache_key)
|
|
|
|
return if !last_log && cache && cache[:ical_url] == ical_url
|
|
|
|
end
|
2016-11-10 15:11:59 +00:00
|
|
|
|
2015-09-23 07:10:07 +00:00
|
|
|
begin
|
2015-09-25 09:48:14 +00:00
|
|
|
events = {}
|
2017-08-24 16:59:53 +00:00
|
|
|
if ical_url.present?
|
|
|
|
events = Calendar.fetch_parse(ical_url)
|
2015-09-25 09:48:14 +00:00
|
|
|
end
|
2015-09-09 06:52:05 +00:00
|
|
|
|
2015-09-23 07:10:07 +00:00
|
|
|
# sync with public_holidays
|
|
|
|
if !public_holidays
|
|
|
|
self.public_holidays = {}
|
2015-09-09 06:52:05 +00:00
|
|
|
end
|
2015-09-25 07:21:55 +00:00
|
|
|
|
|
|
|
# remove old ical entries if feed has changed
|
2017-10-01 12:25:52 +00:00
|
|
|
public_holidays.each do |day, meta|
|
2015-09-25 07:21:55 +00:00
|
|
|
next if !public_holidays[day]['feed']
|
|
|
|
next if meta['feed'] == Digest::MD5.hexdigest(ical_url)
|
|
|
|
public_holidays.delete(day)
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2015-09-25 07:21:55 +00:00
|
|
|
|
|
|
|
# sync new ical feed dates
|
2017-10-01 12:25:52 +00:00
|
|
|
events.each do |day, summary|
|
2015-09-23 07:10:07 +00:00
|
|
|
if !public_holidays[day]
|
|
|
|
public_holidays[day] = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
# ignore if already added or changed
|
|
|
|
next if public_holidays[day].key?('active')
|
|
|
|
|
|
|
|
# create new entry
|
|
|
|
public_holidays[day] = {
|
|
|
|
active: true,
|
|
|
|
summary: summary,
|
2015-09-25 07:21:55 +00:00
|
|
|
feed: Digest::MD5.hexdigest(ical_url)
|
2015-09-23 07:10:07 +00:00
|
|
|
}
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2015-09-23 07:10:07 +00:00
|
|
|
self.last_log = nil
|
2017-08-24 16:59:53 +00:00
|
|
|
if id
|
|
|
|
Cache.write(
|
|
|
|
cache_key,
|
|
|
|
{ public_holidays: public_holidays, ical_url: ical_url },
|
|
|
|
{ expires_in: 1.day },
|
|
|
|
)
|
|
|
|
end
|
2015-09-23 07:10:07 +00:00
|
|
|
rescue => e
|
|
|
|
self.last_log = e.inspect
|
|
|
|
end
|
|
|
|
|
2015-09-09 06:52:05 +00:00
|
|
|
self.last_sync = Time.zone.now
|
2015-09-23 07:10:07 +00:00
|
|
|
if !without_save
|
|
|
|
save
|
|
|
|
end
|
2015-09-09 06:52:05 +00:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2017-08-24 16:59:53 +00:00
|
|
|
def self.fetch_parse(location)
|
2017-11-23 08:09:44 +00:00
|
|
|
if location.match?(/^http/i)
|
2015-09-09 06:52:05 +00:00
|
|
|
result = UserAgent.get(location)
|
2015-09-23 07:10:07 +00:00
|
|
|
if !result.success?
|
2016-03-01 14:26:46 +00:00
|
|
|
raise result.error
|
2015-09-23 07:10:07 +00:00
|
|
|
end
|
2015-09-09 06:52:05 +00:00
|
|
|
cal_file = result.body
|
|
|
|
else
|
|
|
|
cal_file = File.open(location)
|
|
|
|
end
|
|
|
|
|
2016-07-17 23:05:40 +00:00
|
|
|
cals = Icalendar::Calendar.parse(cal_file)
|
2015-09-09 06:52:05 +00:00
|
|
|
cal = cals.first
|
|
|
|
events = {}
|
2017-10-01 12:25:52 +00:00
|
|
|
cal.events.each do |event|
|
2017-08-24 16:59:53 +00:00
|
|
|
if event.rrule
|
|
|
|
|
|
|
|
# loop till days
|
|
|
|
interval_frame_start = Date.parse("#{Time.zone.now - 1.year}-01-01")
|
|
|
|
interval_frame_end = Date.parse("#{Time.zone.now + 3.years}-12-31")
|
|
|
|
occurrences = event.occurrences_between(interval_frame_start, interval_frame_end)
|
|
|
|
if occurrences.present?
|
2017-10-01 12:25:52 +00:00
|
|
|
occurrences.each do |occurrence|
|
2017-08-24 16:59:53 +00:00
|
|
|
result = Calendar.day_and_comment_by_event(event, occurrence.start_time)
|
|
|
|
next if !result
|
|
|
|
events[result[0]] = result[1]
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-08-24 16:59:53 +00:00
|
|
|
end
|
|
|
|
end
|
2015-09-09 06:52:05 +00:00
|
|
|
next if event.dtstart < Time.zone.now - 1.year
|
2015-11-30 13:29:23 +00:00
|
|
|
next if event.dtstart > Time.zone.now + 3.years
|
2017-08-24 16:59:53 +00:00
|
|
|
result = Calendar.day_and_comment_by_event(event, event.dtstart)
|
|
|
|
next if !result
|
|
|
|
events[result[0]] = result[1]
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2015-09-09 06:52:05 +00:00
|
|
|
events.sort.to_h
|
|
|
|
end
|
2015-09-10 19:09:50 +00:00
|
|
|
|
2017-08-24 16:59:53 +00:00
|
|
|
# get day and comment by event
|
|
|
|
def self.day_and_comment_by_event(event, start_time)
|
|
|
|
day = "#{start_time.year}-#{format('%02d', start_time.month)}-#{format('%02d', start_time.day)}"
|
|
|
|
comment = event.summary || event.description
|
2018-06-01 11:32:59 +00:00
|
|
|
comment = comment.to_utf8(fallback: :read_as_sanitized_binary)
|
2017-08-24 16:59:53 +00:00
|
|
|
|
|
|
|
# ignore daylight saving time entries
|
2017-11-23 08:09:44 +00:00
|
|
|
return if comment.match?(/(daylight saving|sommerzeit|summertime)/i)
|
2017-08-24 16:59:53 +00:00
|
|
|
[day, comment]
|
|
|
|
end
|
|
|
|
|
2015-09-10 19:09:50 +00:00
|
|
|
private
|
|
|
|
|
|
|
|
# if changed calendar is default, set all others default to false
|
|
|
|
def sync_default
|
2017-06-16 22:53:20 +00:00
|
|
|
return true if !default
|
2017-10-01 12:25:52 +00:00
|
|
|
Calendar.find_each do |calendar|
|
2015-09-10 19:09:50 +00:00
|
|
|
next if calendar.id == id
|
|
|
|
next if !calendar.default
|
|
|
|
calendar.default = false
|
|
|
|
calendar.save
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-06-16 22:53:20 +00:00
|
|
|
true
|
2015-09-10 19:09:50 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# check if min one is set to default true
|
|
|
|
def min_one_check
|
2016-11-10 15:11:59 +00:00
|
|
|
if !Calendar.find_by(default: true)
|
|
|
|
first = Calendar.order(:created_at, :id).limit(1).first
|
2017-08-24 16:59:53 +00:00
|
|
|
return true if !first
|
2016-11-10 15:11:59 +00:00
|
|
|
first.default = true
|
|
|
|
first.save
|
|
|
|
end
|
2015-09-22 23:22:45 +00:00
|
|
|
|
|
|
|
# check if sla's are refer to an existing calendar
|
2016-11-10 15:11:59 +00:00
|
|
|
default_calendar = Calendar.find_by(default: true)
|
2017-10-01 12:25:52 +00:00
|
|
|
Sla.find_each do |sla|
|
2015-09-22 23:22:45 +00:00
|
|
|
if !sla.calendar_id
|
2016-11-10 15:11:59 +00:00
|
|
|
sla.calendar_id = default_calendar.id
|
|
|
|
sla.save!
|
2015-09-22 23:22:45 +00:00
|
|
|
next
|
|
|
|
end
|
|
|
|
if !Calendar.find_by(id: sla.calendar_id)
|
2016-11-10 15:11:59 +00:00
|
|
|
sla.calendar_id = default_calendar.id
|
|
|
|
sla.save!
|
2015-09-22 23:22:45 +00:00
|
|
|
end
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-06-16 22:53:20 +00:00
|
|
|
true
|
2015-09-10 19:09:50 +00:00
|
|
|
end
|
2015-09-23 07:10:07 +00:00
|
|
|
|
|
|
|
# fetch ical feed
|
|
|
|
def fetch_ical
|
|
|
|
sync(true)
|
2017-06-16 22:53:20 +00:00
|
|
|
true
|
2015-09-23 07:10:07 +00:00
|
|
|
end
|
2015-09-25 07:21:55 +00:00
|
|
|
|
|
|
|
# validate format of public holidays
|
|
|
|
def validate_public_holidays
|
|
|
|
|
|
|
|
# fillup feed info
|
2016-11-10 15:11:59 +00:00
|
|
|
before = public_holidays_was
|
2017-10-01 12:25:52 +00:00
|
|
|
public_holidays.each do |day, meta|
|
2016-11-10 15:11:59 +00:00
|
|
|
if before && before[day] && before[day]['feed']
|
|
|
|
meta['feed'] = before[day]['feed']
|
2015-09-25 07:21:55 +00:00
|
|
|
end
|
2016-01-15 17:22:57 +00:00
|
|
|
meta['active'] = if meta['active']
|
|
|
|
true
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-06-16 22:53:20 +00:00
|
|
|
true
|
2015-09-25 07:21:55 +00:00
|
|
|
end
|
2015-09-09 06:52:05 +00:00
|
|
|
end
|