mno-enterprise-core 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -0
  3. data/Rakefile +12 -0
  4. data/app/assets/images/mno_enterprise/main-logo.png +0 -0
  5. data/app/controllers/mno_enterprise/application_controller.rb +116 -0
  6. data/app/helpers/mno_enterprise/application_helper.rb +67 -0
  7. data/app/helpers/mno_enterprise/impersonate_helper.rb +27 -0
  8. data/app/models/mno_enterprise/ability.rb +6 -0
  9. data/app/models/mno_enterprise/app.rb +72 -0
  10. data/app/models/mno_enterprise/app_instance.rb +36 -0
  11. data/app/models/mno_enterprise/app_instances_sync.rb +6 -0
  12. data/app/models/mno_enterprise/arrears_situation.rb +6 -0
  13. data/app/models/mno_enterprise/audit_event.rb +21 -0
  14. data/app/models/mno_enterprise/base_resource.rb +228 -0
  15. data/app/models/mno_enterprise/credit_card.rb +40 -0
  16. data/app/models/mno_enterprise/deletion_request.rb +35 -0
  17. data/app/models/mno_enterprise/impac/dashboard.rb +36 -0
  18. data/app/models/mno_enterprise/impac/dashboard_provisioner.rb +5 -0
  19. data/app/models/mno_enterprise/impac/kpi.rb +9 -0
  20. data/app/models/mno_enterprise/impac/widget.rb +13 -0
  21. data/app/models/mno_enterprise/invoice.rb +53 -0
  22. data/app/models/mno_enterprise/org_invite.rb +50 -0
  23. data/app/models/mno_enterprise/organization.rb +33 -0
  24. data/app/models/mno_enterprise/team.rb +50 -0
  25. data/app/models/mno_enterprise/tenant.rb +5 -0
  26. data/app/models/mno_enterprise/tenant_invoice.rb +5 -0
  27. data/app/models/mno_enterprise/user.rb +183 -0
  28. data/app/pdf/mno_enterprise/invoice_pdf.rb +516 -0
  29. data/config/initializers/audit_log.rb +5 -0
  30. data/config/locales/devise.en.yml +60 -0
  31. data/config/routes.rb +2 -0
  32. data/config/styleguide.yml +106 -0
  33. data/lib/accountingjs_serializer.rb +51 -0
  34. data/lib/devise/controllers/extension_helpers.rb +52 -0
  35. data/lib/devise/extension_routes.rb +11 -0
  36. data/lib/devise/hooks/password_expirable.rb +5 -0
  37. data/lib/devise/models/password_expirable.rb +28 -0
  38. data/lib/devise/models/remote_authenticatable.rb +48 -0
  39. data/lib/devise/strategies/remote_authenticatable.rb +44 -0
  40. data/lib/devise_extension.rb +36 -0
  41. data/lib/faraday/adapter/net_http_no_proxy.rb +19 -0
  42. data/lib/generators/mno_enterprise/database_extension/USAGE +11 -0
  43. data/lib/generators/mno_enterprise/database_extension/database_extension_generator.rb +36 -0
  44. data/lib/generators/mno_enterprise/database_extension/templates/model.rb +9 -0
  45. data/lib/generators/mno_enterprise/dummy/dummy_generator.rb +98 -0
  46. data/lib/generators/mno_enterprise/dummy/templates/rails/application.rb.erb +9 -0
  47. data/lib/generators/mno_enterprise/dummy/templates/rails/boot.rb.erb +6 -0
  48. data/lib/generators/mno_enterprise/dummy/templates/rails/database.yml +22 -0
  49. data/lib/generators/mno_enterprise/dummy/templates/rails/routes.rb +8 -0
  50. data/lib/generators/mno_enterprise/dummy/templates/rails/test-env.rb +45 -0
  51. data/lib/generators/mno_enterprise/install/install_generator.rb +140 -0
  52. data/lib/generators/mno_enterprise/install/templates/Procfile +1 -0
  53. data/lib/generators/mno_enterprise/install/templates/config/initializers/mno_enterprise.rb +135 -0
  54. data/lib/generators/mno_enterprise/install/templates/config/mno_enterprise_styleguide.yml +104 -0
  55. data/lib/generators/mno_enterprise/install/templates/javascripts/mno_enterprise_extensions.js +7 -0
  56. data/lib/generators/mno_enterprise/install/templates/stylesheets/main.less_erb +25 -0
  57. data/lib/generators/mno_enterprise/install/templates/stylesheets/theme.less_erb +59 -0
  58. data/lib/generators/mno_enterprise/install/templates/stylesheets/variables.less +337 -0
  59. data/lib/generators/mno_enterprise/install/templates/tasks/sprites.rake +14 -0
  60. data/lib/generators/mno_enterprise/puma_stack/puma_stack_generator.rb +58 -0
  61. data/lib/generators/mno_enterprise/templates/scripts/monit/app-server.conf +8 -0
  62. data/lib/generators/mno_enterprise/templates/scripts/nginx/app +51 -0
  63. data/lib/generators/mno_enterprise/templates/scripts/puma.rb +25 -0
  64. data/lib/generators/mno_enterprise/templates/scripts/setup.sh +27 -0
  65. data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web-hotrestart.conf +26 -0
  66. data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web-server.conf +34 -0
  67. data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web.conf +2 -0
  68. data/lib/generators/mno_enterprise/templates/scripts/upstart/app.conf +11 -0
  69. data/lib/her_extension/her_orm_adapter.rb +54 -0
  70. data/lib/her_extension/middleware/mnoe_api_v1_parse_json.rb +54 -0
  71. data/lib/her_extension/model/associations/association.rb +61 -0
  72. data/lib/her_extension/model/associations/association_proxy.rb +34 -0
  73. data/lib/her_extension/model/associations/has_many_association.rb +115 -0
  74. data/lib/her_extension/model/attributes.rb +43 -0
  75. data/lib/her_extension/model/orm.rb +59 -0
  76. data/lib/her_extension/model/parse.rb +40 -0
  77. data/lib/her_extension/model/relation.rb +92 -0
  78. data/lib/her_extension/validations/remote_uniqueness_validation.rb +33 -0
  79. data/lib/html_processor.rb +106 -0
  80. data/lib/mandrill_client.rb +58 -0
  81. data/lib/mno-enterprise-core.rb +1 -0
  82. data/lib/mno_enterprise/concerns.rb +4 -0
  83. data/lib/mno_enterprise/concerns/controllers.rb +6 -0
  84. data/lib/mno_enterprise/concerns/controllers/angular_csrf.rb +59 -0
  85. data/lib/mno_enterprise/concerns/controllers/auth.rb +9 -0
  86. data/lib/mno_enterprise/concerns/controllers/auth/confirmations_controller.rb +187 -0
  87. data/lib/mno_enterprise/concerns/controllers/auth/passwords_controller.rb +54 -0
  88. data/lib/mno_enterprise/concerns/controllers/auth/registrations_controller.rb +136 -0
  89. data/lib/mno_enterprise/concerns/controllers/auth/sessions_controller.rb +54 -0
  90. data/lib/mno_enterprise/concerns/controllers/auth/unlocks_controller.rb +50 -0
  91. data/lib/mno_enterprise/concerns/models.rb +6 -0
  92. data/lib/mno_enterprise/concerns/models/ability.rb +108 -0
  93. data/lib/mno_enterprise/concerns/models/app_instance.rb +100 -0
  94. data/lib/mno_enterprise/concerns/models/organization.rb +102 -0
  95. data/lib/mno_enterprise/core.rb +279 -0
  96. data/lib/mno_enterprise/database_extendable.rb +57 -0
  97. data/lib/mno_enterprise/engine.rb +33 -0
  98. data/lib/mno_enterprise/testing_support/ability_test_helper.rb +10 -0
  99. data/lib/mno_enterprise/testing_support/common_rake.rb +19 -0
  100. data/lib/mno_enterprise/testing_support/factories.rb +13 -0
  101. data/lib/mno_enterprise/testing_support/factories/app_instances.rb +30 -0
  102. data/lib/mno_enterprise/testing_support/factories/apps.rb +45 -0
  103. data/lib/mno_enterprise/testing_support/factories/arrears_situation.rb +14 -0
  104. data/lib/mno_enterprise/testing_support/factories/audit_event.rb +15 -0
  105. data/lib/mno_enterprise/testing_support/factories/credit_card.rb +33 -0
  106. data/lib/mno_enterprise/testing_support/factories/deletion_request.rb +17 -0
  107. data/lib/mno_enterprise/testing_support/factories/impac/dashboards.rb +15 -0
  108. data/lib/mno_enterprise/testing_support/factories/impac/kpis.rb +20 -0
  109. data/lib/mno_enterprise/testing_support/factories/impac/widgets.rb +15 -0
  110. data/lib/mno_enterprise/testing_support/factories/invoices.rb +51 -0
  111. data/lib/mno_enterprise/testing_support/factories/org_invite.rb +24 -0
  112. data/lib/mno_enterprise/testing_support/factories/organizations.rb +25 -0
  113. data/lib/mno_enterprise/testing_support/factories/team.rb +17 -0
  114. data/lib/mno_enterprise/testing_support/factories/tenant.rb +12 -0
  115. data/lib/mno_enterprise/testing_support/factories/tenant_invoice.rb +29 -0
  116. data/lib/mno_enterprise/testing_support/factories/users.rb +48 -0
  117. data/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb +49 -0
  118. data/lib/mno_enterprise/testing_support/mno_enterprise_api_test_helper.rb +167 -0
  119. data/lib/mno_enterprise/testing_support/mnoe_faraday_test_adapter.rb +173 -0
  120. data/lib/mno_enterprise/testing_support/organizations_shared_helpers.rb +175 -0
  121. data/lib/mno_enterprise/testing_support/user_action_shared.rb +47 -0
  122. data/lib/mno_enterprise/version.rb +3 -0
  123. data/lib/tasks/mno_enterprise_tasks.rake +22 -0
  124. data/spec/controllers/mno_enterprise/angular_csrf_spec.rb +42 -0
  125. data/spec/lib/her_extension/her_orm_adapter.rb +7 -0
  126. data/spec/lib/her_extension/model/relation_spec.rb +7 -0
  127. data/spec/lib/mandrill_client_spec.rb +64 -0
  128. data/spec/mno_enterprise_spec.rb +79 -0
  129. data/spec/models/mno_enterprise/app_instance_spec.rb +7 -0
  130. data/spec/models/mno_enterprise/app_spec.rb +62 -0
  131. data/spec/models/mno_enterprise/base_resource_spec.rb +28 -0
  132. data/spec/models/mno_enterprise/deletion_request_spec.rb +26 -0
  133. data/spec/models/mno_enterprise/invoice_spec.rb +7 -0
  134. data/spec/models/mno_enterprise/organization_spec.rb +7 -0
  135. data/spec/models/mno_enterprise/user_spec.rb +44 -0
  136. data/spec/rails_helper.rb +73 -0
  137. data/spec/spec_helper.rb +78 -0
  138. metadata +421 -0
@@ -0,0 +1,115 @@
1
+ # PR:
2
+ # fixed options loading in dynamically defined method
3
+ # changed build to not add the parent key automatically
4
+ # changed create to send raw parameters instead of doing smart stuff
5
+ module Her
6
+ module Model
7
+ module Associations
8
+ class HasManyAssociation < Association
9
+
10
+ # @private
11
+ def self.attach(klass, name, opts)
12
+ opts = {
13
+ :class_name => name.to_s.classify,
14
+ :name => name,
15
+ :data_key => name,
16
+ :default => Her::Collection.new,
17
+ :path => "/#{name}",
18
+ :inverse_of => nil
19
+ }.merge(opts)
20
+ klass.associations[:has_many] << opts
21
+
22
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
23
+ def #{name}
24
+ cached_name = :"@_her_association_#{name}"
25
+ cached_data = (instance_variable_defined?(cached_name) && instance_variable_get(cached_name))
26
+ opts = Marshal.load(#{Marshal.dump(opts).inspect})
27
+ cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasManyAssociation.proxy(self, opts))
28
+ end
29
+ RUBY
30
+ end
31
+
32
+ # Initialize a new object with a foreign key to the parent
33
+ #
34
+ # @example
35
+ # class User
36
+ # include Her::Model
37
+ # has_many :comments
38
+ # end
39
+ #
40
+ # class Comment
41
+ # include Her::Model
42
+ # end
43
+ #
44
+ # user = User.find(1)
45
+ # new_comment = user.comments.build(:body => "Hello!")
46
+ # new_comment # => #<Comment body="Hello!">
47
+ #
48
+ def build(attributes = {})
49
+ @klass.build(attributes)
50
+ end
51
+
52
+ # Post an object to the nested resource collection endpoint then
53
+ # refetch the nested collection
54
+ #
55
+ # @example
56
+ # class User
57
+ # include Her::Model
58
+ # has_many :comments
59
+ # end
60
+ #
61
+ # class Comment
62
+ # include Her::Model
63
+ # end
64
+ #
65
+ # user = User.find(1)
66
+ # user.comments.create(:body => "Hello!")
67
+ # user.comments # => [#<Comment id=2 user_id=1 body="Hello!">]
68
+ def create(attributes = {})
69
+ resp = self.execute_request(:create,attributes)
70
+ @klass.build(resp)
71
+ end
72
+
73
+ # Consider removing - not sure this method on a has_many collection has any meaning
74
+ def update(attributes = {})
75
+ self.execute_request(:update,attributes)
76
+ end
77
+
78
+ # Consider removing - not sure this method on a has_many collection has any meaning
79
+ def destroy(attributes = {})
80
+ self.execute_request(:destroy,attributes)
81
+ end
82
+
83
+ def execute_request(action, attrs)
84
+ attributes = HashWithIndifferentAccess.new(attrs)
85
+
86
+ # Post data to the collection endpoint
87
+ resource = nil
88
+ path = self.build_request_path
89
+ method = self.method_for(action.to_sym)
90
+
91
+ # Add ID to path if resource method
92
+ if [:put, :patch, :delete].include?(method.to_sym)
93
+ path += "/#{attributes[@klass.primary_key]}"
94
+ end
95
+
96
+ params = self.to_params(attributes).merge(:_method => method, :_path => path)
97
+ self.request(params) do |parsed_data, response|
98
+ resource = parsed_data if response.success?
99
+ end
100
+
101
+ # Reload nested collection
102
+ self.reload if resource
103
+
104
+ resource
105
+ end
106
+
107
+ # Override fetch to not do any smart stuff...
108
+ def fetch
109
+ super
110
+ end
111
+
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,43 @@
1
+ module Her
2
+ module Model
3
+ # TODO: make PR to Her project
4
+ # This patch fixes the detection of changed attributes
5
+ module Attributes
6
+ extend ActiveSupport::Concern
7
+
8
+
9
+ module ClassMethods
10
+ # Define the attributes that will be used to track dirty attributes and validations
11
+ #
12
+ # @param [Array] attributes
13
+ # @example
14
+ # class User
15
+ # include Her::Model
16
+ # attributes :name, :email
17
+ # end
18
+ def attributes(*attributes)
19
+ define_attribute_methods attributes
20
+
21
+ attributes.each do |attribute|
22
+ attribute = attribute.to_sym
23
+
24
+ unless instance_methods.include?(:"#{attribute}=")
25
+ define_method("#{attribute}=") do |value|
26
+ @attributes[:"#{attribute}"] = nil unless @attributes.include?(:"#{attribute}")
27
+ self.send(:"#{attribute}_will_change!") if @attributes[:"#{attribute}"] != value
28
+ @attributes[:"#{attribute}"] = value
29
+ end
30
+ end
31
+
32
+ unless instance_methods.include?(:"#{attribute}?")
33
+ define_method("#{attribute}?") do
34
+ @attributes.include?(:"#{attribute}") && @attributes[:"#{attribute}"].present?
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,59 @@
1
+ # FIX: Reset params when blank_relation is called so that query statement (like where)
2
+ # automatically start fresh
3
+ #
4
+ # FIX: scope to create real isolated scopes for each Her::Model
5
+ #
6
+ # FIX: add put method on model
7
+ module Her
8
+ module Model
9
+ module ORM
10
+
11
+ # Send raw PUT request to model - no data encapsulation performed
12
+ def put(attrs)
13
+ method = :put
14
+ self.class.request(attrs.merge(:_method => method, :_path => request_path)) do |parsed_data, response|
15
+ return parsed_data
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ # Create a new chainable scope
21
+ #
22
+ # @example
23
+ # class User
24
+ # include Her::Model
25
+ #
26
+ # scope :admins, lambda { where(:admin => 1) }
27
+ # scope :page, lambda { |page| where(:page => page) }
28
+ # enc
29
+ #
30
+ # User.admins # Called via GET "/users?admin=1"
31
+ # User.page(2).all # Called via GET "/users?page=2"
32
+ def scope(name, code)
33
+
34
+ # Add the scope method to the class
35
+ metaclass = (class << self; self end)
36
+ metaclass.send(:define_method, name) do |*args|
37
+ instance_exec(*args, &code)
38
+ end
39
+ Relation.scopes["#{self.to_s}.#{name}"] = code
40
+
41
+ # Add the scope method to the Relation class
42
+ Relation.class_eval <<-RUBY, __FILE__, __LINE__ + 1
43
+ def #{name}(*args)
44
+ parent_klass = @parent.instance_variable_get("@klass") || @parent.to_s
45
+ instance_exec(*args,&self.class.scopes["\#{parent_klass}.#{name}"])
46
+ end
47
+ RUBY
48
+ end
49
+
50
+ private
51
+ def blank_relation
52
+ @blank_relation ||= Relation.new(self)
53
+ @blank_relation.params = {}
54
+ @blank_relation
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,40 @@
1
+ # TODO: PR to HER
2
+ # Fix to_params when embeded_params is nil
3
+ # Fix to_params when changes is nil
4
+ # --> allow all params - this is required to be able to make class level
5
+ # requests like MyModel.post(path,{some: 'data'})
6
+ #
7
+ module Her
8
+ module Model
9
+ # This module handles resource data parsing at the model level (after the parsing middleware)
10
+ module Parse
11
+ extend ActiveSupport::Concern
12
+
13
+ module ClassMethods
14
+
15
+ # @private
16
+ def to_params(attributes, changes = nil)
17
+ filtered_attributes = attributes.dup.symbolize_keys
18
+ filtered_attributes.merge!(embeded_params(attributes) || {})
19
+ if her_api && her_api.options[:send_only_modified_attributes] && !changes.nil?
20
+ filtered_attributes = changes.symbolize_keys.keys.inject({}) do |hash, attribute|
21
+ hash[attribute] = filtered_attributes[attribute]
22
+ hash
23
+ end
24
+ end
25
+
26
+ if include_root_in_json?
27
+ if json_api_format?
28
+ { included_root_element => [filtered_attributes] }
29
+ else
30
+ { included_root_element => filtered_attributes }
31
+ end
32
+ else
33
+ filtered_attributes
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,92 @@
1
+ require 'digest/md5'
2
+
3
+ # TODO: we should do a PR to the HER project which includes
4
+ # smarter filtering and more reliable caching
5
+ # Before doing so, we make the filtering behavior customizable
6
+ # (E.g.: configure name of filter/sort/limit keys)
7
+ module Her
8
+ module Model
9
+ class Relation
10
+ def initialize(parent)
11
+ @parent = parent
12
+ @params = {}
13
+ end
14
+
15
+ # Fetch a collection of resources
16
+ def fetch
17
+ path = @parent.build_request_path(@params)
18
+ method = @parent.method_for(:find)
19
+ @parent.request(@params.merge(:_method => method, :_path => path)) do |parsed_data, response|
20
+ @parent.new_collection(parsed_data)
21
+ end
22
+ end
23
+
24
+ # Override Her::Model::Relation#where
25
+ # to follow jsonapi.org standards
26
+ # Use filter instead of raw parameters
27
+ def where(params = {})
28
+ return self if !params || params.empty?
29
+ self.params[:filter] ||= {}
30
+ self.params[:filter].merge!(params)
31
+ self
32
+ end
33
+ alias all where
34
+
35
+ # E.g:
36
+ # Product.order_by('created_at.desc','name.asc')
37
+ def order_by(*args)
38
+ return self if args.empty?
39
+ self.params[:sort] = [self.params[:sort],args].flatten.compact.uniq
40
+ self
41
+ end
42
+ alias sort_by order_by
43
+
44
+ # ActiveRecord-like order
45
+ # Product.order("created_at DESC, name ASC")
46
+ def order(string_query)
47
+ return self if !string_query || string_query.empty?
48
+ args = string_query.split(',').map do |q|
49
+ field, direction = q.strip.split(/\s+/).compact
50
+ [field, direction ? direction.downcase : nil].join('.')
51
+ end
52
+ self.order_by(*args)
53
+ end
54
+ alias sort order
55
+
56
+ # Limit the number of results returned
57
+ def limit(max = nil)
58
+ return self if !max
59
+ self.params[:limit] = max
60
+ self
61
+ end
62
+
63
+ # Skip result rows
64
+ def skip(nrows = nil)
65
+ return self if !nrows
66
+ self.params[:skip] = nrows
67
+ self
68
+ end
69
+
70
+ # Reset the query parameters
71
+ def reset_params
72
+ @params.clear
73
+ end
74
+
75
+ # Refetch the relation
76
+ def reload
77
+ fetch
78
+ end
79
+
80
+ # Hold the scopes defined on Her::Model classes
81
+ # E.g.: scope :admin, -> { where(admin: 1) }
82
+ def self.scopes
83
+ @scopes ||= {}
84
+ end
85
+
86
+ private
87
+ def query_checkum
88
+ Digest::MD5.hexdigest(Marshal.dump(@params))
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,33 @@
1
+ # TODO:
2
+ # - spec module
3
+ # - spec that validator ignores records with the same id
4
+ module HerExtension
5
+ module Validations
6
+
7
+ # Validate the uniqueness of a field by performing a remote API call
8
+ class RemoteUniquenessValidator < ::ActiveModel::EachValidator
9
+ def validate_each(record,attribute,value)
10
+ list = record.class.where({ attribute => value }).limit(1)
11
+
12
+ if list.reject { |e| e.id == record.id }.any?
13
+ error_options = options.except(:case_sensitive, :scope, :conditions)
14
+ error_options[:value] = value
15
+ record.errors.add(attribute, :taken, error_options)
16
+ end
17
+ end
18
+ end
19
+
20
+ # This module overrides validates_uniqueness_of
21
+ module RemoteUniquenessValidation
22
+ def self.included(base)
23
+ base.extend(ClassMethods)
24
+ end
25
+
26
+ module ClassMethods
27
+ def validates_uniqueness_of(*attr_names)
28
+ validates_with RemoteUniquenessValidator, _merge_attributes(attr_names)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,106 @@
1
+ require 'redcarpet'
2
+ require 'nokogiri'
3
+ require 'sanitize'
4
+
5
+ # This utility class is used to
6
+ # work on html text
7
+ #
8
+ # You can initialize it with html or markdown text
9
+ class HtmlProcessor
10
+ attr_reader :html, :original
11
+
12
+ #======================================
13
+ # Constants
14
+ #======================================
15
+ DESCRIPTION_PROCESSING_ORDER = %w( p h1 h2 h3 h4 h5 h6 )
16
+
17
+
18
+ # Define Youtube transformer for Sanitize
19
+ YOUTUBE_TRANSFORMER = lambda do |env|
20
+ node = env[:node]
21
+ node_name = env[:node_name]
22
+
23
+ # Don't continue if this node is already whitelisted or is not an element.
24
+ return if env[:is_whitelisted] || !node.element?
25
+
26
+ # Don't continue unless the node is an iframe.
27
+ return unless node_name == 'iframe'
28
+
29
+ # Verify that the video URL is actually a valid YouTube video URL.
30
+ return unless node['src'] =~ %r|\A(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/|
31
+
32
+ # We're now certain that this is a YouTube embed, but we still need to run
33
+ # it through a special Sanitize step to ensure that no unwanted elements or
34
+ # attributes that don't belong in a YouTube embed can sneak in.
35
+ Sanitize.node!(node, {
36
+ :elements => %w[iframe],
37
+
38
+ :attributes => {
39
+ 'iframe' => %w[allowfullscreen frameborder height src width]
40
+ }
41
+ })
42
+
43
+ # Now that we're sure that this is a valid YouTube embed and that there are
44
+ # no unwanted elements or attributes hidden inside it, we can tell Sanitize
45
+ # to whitelist the current node.
46
+ {:node_whitelist => [node]}
47
+ end
48
+
49
+ # Default options for Sanitize
50
+ SANITIZER_OPTS = Sanitize::Config::RELAXED.merge(
51
+ attributes: Sanitize::Config::RELAXED[:attributes].merge(
52
+ 'a' => %w[href hreflang name rel target],
53
+ ),
54
+ transformers: YOUTUBE_TRANSFORMER
55
+ )
56
+
57
+ #======================================
58
+ # Methods
59
+ #======================================
60
+ def initialize(text, options = { })
61
+ @original = text
62
+
63
+ # Process markdown or leave original
64
+ if options[:format].to_s == 'markdown' && text
65
+ html_options = { :safe_links_only => true, :hard_wrap => true, :filter_html => false }
66
+ renderer_options = { :autolink => true, :no_intraemphasis => true, :fenced_code_blocks => true, :superscript => true }
67
+
68
+ renderer = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(html_options), renderer_options)
69
+ raw_html = renderer.render(text)
70
+ @html = Sanitize.fragment(raw_html, SANITIZER_OPTS)
71
+ else
72
+ @html = text
73
+ end
74
+ end
75
+
76
+ # Return a Nokogiri document based
77
+ # on processor html
78
+ def document
79
+ @document ||= Nokogiri::HTML(@html)
80
+ end
81
+
82
+ # Return a description of the document
83
+ # by returning the first sentence of the
84
+ # first DESCRIPTION_PROCESSING_ORDER found
85
+ def description
86
+ # Return cached value if one
87
+ return @description if @description
88
+
89
+ # Parse the html document to try to find
90
+ # a description
91
+ @description = ''
92
+ DESCRIPTION_PROCESSING_ORDER.each do |selector|
93
+ elem = self.document.css(selector).select { |e| e && !e.content.blank? }.first
94
+ next if elem.blank? #skip if nil or empty
95
+
96
+ # Try to get the first two sentences
97
+ match = elem.content.match(/([^.!?]+[.!?]?)([^.!?]+[.!?]?)?/)
98
+ if match && match.captures.any?
99
+ @description = match.captures.compact.join('')
100
+ end
101
+ break if !@description.empty?
102
+ end
103
+
104
+ return @description
105
+ end
106
+ end