renderer.rb 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. class NotificationFactory::Renderer
  2. =begin
  3. examples how to use
  4. message_subject = NotificationFactory::Renderer.new(
  5. objects: {
  6. ticket: Ticket.first,
  7. },
  8. locale: 'de-de',
  9. timezone: 'America/Port-au-Prince',
  10. template: 'some template <b>#{ticket.title}</b> {config.fqdn}',
  11. escape: false
  12. ).render
  13. message_body = NotificationFactory::Renderer.new(
  14. objects: {
  15. ticket: Ticket.first,
  16. },
  17. locale: 'de-de',
  18. timezone: 'America/Port-au-Prince',
  19. template: 'some template <b>#{ticket.title}</b> #{config.fqdn}',
  20. ).render
  21. =end
  22. def initialize(objects:, locale: nil, timezone: nil, template:, escape: true)
  23. @objects = objects
  24. @locale = locale || Locale.default
  25. @timezone = timezone || Setting.get('timezone_default')
  26. @template = NotificationFactory::Template.new(template, escape)
  27. @escape = escape
  28. end
  29. def render
  30. ERB.new(@template.to_s).result(binding)
  31. end
  32. # d - data of object
  33. # d('user.firstname', htmlEscape)
  34. def d(key, escape = nil)
  35. # do validation, ignore some methods
  36. return "\#{#{key} / not allowed}" if !data_key_valid?(key)
  37. # aliases
  38. map = {
  39. 'article.body' => 'article.body_as_text_with_quote.text2html',
  40. }
  41. if map[key]
  42. key = map[key]
  43. end
  44. # escape in html mode
  45. if escape
  46. no_escape = {
  47. 'article.body_as_html' => true,
  48. 'article.body_as_text_with_quote.text2html' => true,
  49. }
  50. if no_escape[key]
  51. escape = false
  52. end
  53. end
  54. value = nil
  55. object_methods = key.split('.')
  56. object_name = object_methods.shift
  57. # if no object is given, just return
  58. return '#{no such object}' if object_name.blank? # rubocop:disable Lint/InterpolationCheck
  59. object_refs = @objects[object_name] || @objects[object_name.to_sym]
  60. # if object is not in available objects, just return
  61. return "\#{#{object_name} / no such object}" if !object_refs
  62. # if content of method is a complex datatype, just return
  63. if object_methods.blank? && object_refs.class != String && object_refs.class != Float && object_refs.class != Integer
  64. return "\#{#{key} / no such method}"
  65. end
  66. previous_object_refs = ''
  67. object_methods_s = ''
  68. object_methods.each do |method_raw|
  69. method = method_raw.strip
  70. if method == 'value'
  71. temp = object_refs
  72. object_refs = display_value(previous_object_refs, method, object_methods_s, object_refs)
  73. previous_object_refs = temp
  74. end
  75. if object_methods_s != ''
  76. object_methods_s += '.'
  77. end
  78. object_methods_s += method
  79. next if method == 'value'
  80. if object_methods_s == ''
  81. value = "\#{#{object_name}.#{object_methods_s} / no such method}"
  82. break
  83. end
  84. arguments = nil
  85. if /\A(?<method_id>[^(]+)\((?<parameter>[^)]+)\)\z/ =~ method
  86. if parameter != parameter.to_i.to_s
  87. value = "\#{#{object_name}.#{object_methods_s} / invalid parameter: #{parameter}}"
  88. break
  89. end
  90. begin
  91. arguments = Array(parameter.to_i)
  92. method = method_id
  93. rescue
  94. value = "\#{#{object_name}.#{object_methods_s} / #{e.message}}"
  95. break
  96. end
  97. end
  98. # if method exists
  99. if !object_refs.respond_to?(method.to_sym)
  100. value = "\#{#{object_name}.#{object_methods_s} / no such method}"
  101. break
  102. end
  103. begin
  104. previous_object_refs = object_refs
  105. object_refs = object_refs.send(method.to_sym, *arguments)
  106. # body_as_html should trigger the cloning of all inline attachments from the parent article (issue #2399)
  107. if method.to_sym == :body_as_html && previous_object_refs.respond_to?(:should_clone_inline_attachments)
  108. previous_object_refs.should_clone_inline_attachments = true
  109. end
  110. rescue => e
  111. value = "\#{#{object_name}.#{object_methods_s} / #{e.message}}"
  112. break
  113. end
  114. end
  115. placeholder = if !value
  116. object_refs
  117. else
  118. value
  119. end
  120. escaping(convert_to_timezone(placeholder), escape)
  121. end
  122. # c - config
  123. # c('fqdn', htmlEscape)
  124. def c(key, escape = nil)
  125. config = Setting.get(key)
  126. escaping(config, escape)
  127. end
  128. # t - translation
  129. # t('yes', htmlEscape)
  130. def t(key, escape = nil)
  131. translation = Translation.translate(@locale, key)
  132. escaping(translation, escape)
  133. end
  134. # h - htmlEscape
  135. # h(htmlEscape)
  136. def h(value)
  137. return value if !value
  138. CGI.escapeHTML(convert_to_timezone(value).to_s)
  139. end
  140. private
  141. def convert_to_timezone(value)
  142. return Translation.timestamp(@locale, @timezone, value) if value.class == ActiveSupport::TimeWithZone
  143. return Translation.date(@locale, value) if value.class == Date
  144. value
  145. end
  146. def escaping(key, escape)
  147. return key if escape == false
  148. return key if escape.nil? && !@escape
  149. h key
  150. end
  151. def data_key_valid?(key)
  152. return false if key =~ /`|\.(|\s*)(save|destroy|delete|remove|drop|update|create|new|all|where|find|raise|dump|rollback|freeze)/i && key !~ /(update|create)d_(at|by)/i
  153. true
  154. end
  155. def display_value(object, method_name, previous_method_names, key)
  156. return key if method_name != 'value' ||
  157. !key.instance_of?(String)
  158. attributes = ObjectManager::Attribute
  159. .where(object_lookup_id: ObjectLookup.by_name(object.class.to_s))
  160. .where(name: previous_method_names.split('.').last)
  161. return key if attributes.count.zero? || attributes.first.data_type != 'select'
  162. attributes.first.data_option['options'][key] || key
  163. end
  164. end