mno-enterprise-core 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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