calendar.rb 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class Calendar < ApplicationModel
  3. include ChecksClientNotification
  4. include CanUniqName
  5. store :business_hours
  6. store :public_holidays
  7. before_create :validate_public_holidays, :fetch_ical
  8. before_update :validate_public_holidays, :fetch_ical
  9. after_create :sync_default, :min_one_check
  10. after_update :sync_default, :min_one_check
  11. after_destroy :min_one_check
  12. =begin
  13. set inital default calendar
  14. calendar = Calendar.init_setup
  15. returns calendar object
  16. =end
  17. def self.init_setup(ip = nil)
  18. # ignore client ip if not public ip
  19. if ip && ip =~ /^(::1|127\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.|192\.168\.)/
  20. ip = nil
  21. end
  22. # prevent multible setups for same ip
  23. cache = Cache.get('Calendar.init_setup.done')
  24. return if cache && cache[:ip] == ip
  25. Cache.write('Calendar.init_setup.done', { ip: ip }, { expires_in: 1.hour })
  26. # call for calendar suggestion
  27. calendar_details = Service::GeoCalendar.location(ip)
  28. return if !calendar_details
  29. calendar_details['name'] = Calendar.generate_uniq_name(calendar_details['name'])
  30. calendar_details['default'] = true
  31. calendar_details['created_by_id'] = 1
  32. calendar_details['updated_by_id'] = 1
  33. # find if auto generated calendar exists
  34. calendar = Calendar.find_by(default: true, updated_by_id: 1, created_by_id: 1)
  35. if calendar
  36. calendar.update!(calendar_details)
  37. return calendar
  38. end
  39. create(calendar_details)
  40. end
  41. =begin
  42. get default calendar
  43. calendar = Calendar.default
  44. returns calendar object
  45. =end
  46. def self.default
  47. find_by(default: true)
  48. end
  49. =begin
  50. returns preset of ical feeds
  51. feeds = Calendar.ical_feeds
  52. returns
  53. {
  54. 'http://www.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics' => 'US',
  55. ...
  56. }
  57. =end
  58. def self.ical_feeds
  59. data = YAML.load_file(Rails.root.join('config', 'holiday_calendars.yml'))
  60. url = data['url']
  61. data['countries'].map do |country, domain|
  62. [format(url, domain: domain), country]
  63. end.to_h
  64. end
  65. =begin
  66. get list of available timezones and UTC offsets
  67. list = Calendar.timezones
  68. returns
  69. {
  70. 'America/Los_Angeles' => -7
  71. ...
  72. }
  73. =end
  74. def self.timezones
  75. list = {}
  76. TZInfo::Timezone.all_country_zone_identifiers.each do |timezone|
  77. t = TZInfo::Timezone.get(timezone)
  78. diff = t.current_period.utc_total_offset / 60 / 60
  79. list[ timezone ] = diff
  80. end
  81. list
  82. end
  83. =begin
  84. syn all calendars with ical feeds
  85. success = Calendar.sync
  86. returns
  87. true # or false
  88. =end
  89. def self.sync
  90. Calendar.find_each(&:sync)
  91. true
  92. end
  93. =begin
  94. syn one calendars with ical feed
  95. calendar = Calendar.find(4711)
  96. success = calendar.sync
  97. returns
  98. true # or false
  99. =end
  100. def sync(without_save = nil)
  101. return if !ical_url
  102. # only sync every 5 days
  103. if id
  104. cache_key = "CalendarIcal::#{id}"
  105. cache = Cache.get(cache_key)
  106. return if !last_log && cache && cache[:ical_url] == ical_url
  107. end
  108. begin
  109. events = {}
  110. if ical_url.present?
  111. events = Calendar.fetch_parse(ical_url)
  112. end
  113. # sync with public_holidays
  114. if !public_holidays
  115. self.public_holidays = {}
  116. end
  117. # remove old ical entries if feed has changed
  118. public_holidays.each do |day, meta|
  119. next if !public_holidays[day]['feed']
  120. next if meta['feed'] == Digest::MD5.hexdigest(ical_url)
  121. public_holidays.delete(day)
  122. end
  123. # sync new ical feed dates
  124. events.each do |day, summary|
  125. if !public_holidays[day]
  126. public_holidays[day] = {}
  127. end
  128. # ignore if already added or changed
  129. next if public_holidays[day].key?('active')
  130. # create new entry
  131. public_holidays[day] = {
  132. active: true,
  133. summary: summary,
  134. feed: Digest::MD5.hexdigest(ical_url)
  135. }
  136. end
  137. self.last_log = nil
  138. if id
  139. Cache.write(
  140. cache_key,
  141. { public_holidays: public_holidays, ical_url: ical_url },
  142. { expires_in: 1.day },
  143. )
  144. end
  145. rescue => e
  146. self.last_log = e.inspect
  147. end
  148. self.last_sync = Time.zone.now
  149. if !without_save
  150. save
  151. end
  152. true
  153. end
  154. def self.fetch_parse(location)
  155. if location.match?(/^http/i)
  156. result = UserAgent.get(location)
  157. if !result.success?
  158. raise result.error
  159. end
  160. cal_file = result.body
  161. else
  162. cal_file = File.open(location)
  163. end
  164. cals = Icalendar::Calendar.parse(cal_file)
  165. cal = cals.first
  166. events = {}
  167. cal.events.each do |event|
  168. if event.rrule
  169. # loop till days
  170. interval_frame_start = Date.parse("#{Time.zone.now - 1.year}-01-01")
  171. interval_frame_end = Date.parse("#{Time.zone.now + 3.years}-12-31")
  172. occurrences = event.occurrences_between(interval_frame_start, interval_frame_end)
  173. if occurrences.present?
  174. occurrences.each do |occurrence|
  175. result = Calendar.day_and_comment_by_event(event, occurrence.start_time)
  176. next if !result
  177. events[result[0]] = result[1]
  178. end
  179. end
  180. end
  181. next if event.dtstart < Time.zone.now - 1.year
  182. next if event.dtstart > Time.zone.now + 3.years
  183. result = Calendar.day_and_comment_by_event(event, event.dtstart)
  184. next if !result
  185. events[result[0]] = result[1]
  186. end
  187. events.sort.to_h
  188. end
  189. # get day and comment by event
  190. def self.day_and_comment_by_event(event, start_time)
  191. day = "#{start_time.year}-#{format('%02d', start_time.month)}-#{format('%02d', start_time.day)}"
  192. comment = event.summary || event.description
  193. comment = Encode.conv( 'utf8', comment.to_s.force_encoding('utf-8') )
  194. if !comment.valid_encoding?
  195. comment = comment.encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?')
  196. end
  197. # ignore daylight saving time entries
  198. return if comment.match?(/(daylight saving|sommerzeit|summertime)/i)
  199. [day, comment]
  200. end
  201. private
  202. # if changed calendar is default, set all others default to false
  203. def sync_default
  204. return true if !default
  205. Calendar.find_each do |calendar|
  206. next if calendar.id == id
  207. next if !calendar.default
  208. calendar.default = false
  209. calendar.save
  210. end
  211. true
  212. end
  213. # check if min one is set to default true
  214. def min_one_check
  215. if !Calendar.find_by(default: true)
  216. first = Calendar.order(:created_at, :id).limit(1).first
  217. return true if !first
  218. first.default = true
  219. first.save
  220. end
  221. # check if sla's are refer to an existing calendar
  222. default_calendar = Calendar.find_by(default: true)
  223. Sla.find_each do |sla|
  224. if !sla.calendar_id
  225. sla.calendar_id = default_calendar.id
  226. sla.save!
  227. next
  228. end
  229. if !Calendar.find_by(id: sla.calendar_id)
  230. sla.calendar_id = default_calendar.id
  231. sla.save!
  232. end
  233. end
  234. true
  235. end
  236. # fetch ical feed
  237. def fetch_ical
  238. sync(true)
  239. true
  240. end
  241. # validate format of public holidays
  242. def validate_public_holidays
  243. # fillup feed info
  244. before = public_holidays_was
  245. public_holidays.each do |day, meta|
  246. if before && before[day] && before[day]['feed']
  247. meta['feed'] = before[day]['feed']
  248. end
  249. meta['active'] = if meta['active']
  250. true
  251. else
  252. false
  253. end
  254. end
  255. true
  256. end
  257. end