rad_kit 0.0.1
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.
- data/Rakefile +11 -0
- data/lib/components/kit.rb +16 -0
- data/lib/components/kit.yml +3 -0
- data/lib/components/models.rb +7 -0
- data/lib/kit/factories.rb +9 -0
- data/lib/kit/gems.rb +16 -0
- data/lib/kit/http_controller.rb +4 -0
- data/lib/kit/http_controller/authorized.rb +51 -0
- data/lib/kit/http_controller/localized.rb +13 -0
- data/lib/kit/kit.rb +29 -0
- data/lib/kit/models.rb +8 -0
- data/lib/kit/models/attachment_uploader.rb +15 -0
- data/lib/kit/models/attachments_uploader_helper.rb +79 -0
- data/lib/kit/models/authorized.rb +188 -0
- data/lib/kit/models/authorized_object.rb +167 -0
- data/lib/kit/models/default_permissions.yml +29 -0
- data/lib/kit/models/file_uploader.rb +26 -0
- data/lib/kit/models/micelaneous.rb +1 -0
- data/lib/kit/models/role.rb +88 -0
- data/lib/kit/models_after.rb +27 -0
- data/lib/kit/mongoid.rb +22 -0
- data/lib/kit/mongoid/rad_micelaneous.rb +36 -0
- data/lib/kit/mongoid/text_processor.rb +44 -0
- data/lib/kit/spec.rb +77 -0
- data/lib/kit/spec/items_controller_crud.rb +64 -0
- data/lib/kit/support.rb +14 -0
- data/lib/kit/support/string.rb +6 -0
- data/lib/kit/tasks.rb +18 -0
- data/lib/kit/text_utils.rb +43 -0
- data/lib/kit/text_utils/code_highlighter.rb +58 -0
- data/lib/kit/text_utils/custom_markdown.rb +90 -0
- data/lib/kit/text_utils/ensure_utf.rb +8 -0
- data/lib/kit/text_utils/github_flavoured_markdown.rb +32 -0
- data/lib/kit/text_utils/html_sanitizer.rb +89 -0
- data/lib/kit/text_utils/image_box.rb +35 -0
- data/lib/kit/text_utils/markup.rb +43 -0
- data/lib/kit/text_utils/processor.rb +25 -0
- data/lib/kit/text_utils/tag_shortcuts.rb +14 -0
- data/lib/kit/text_utils/truncate.rb +29 -0
- data/lib/kit/text_utils/truncator.rb +15 -0
- data/lib/kit/text_utils/urls.rb +13 -0
- data/readme.md +10 -0
- data/spec/controller/authorization_spec.rb +149 -0
- data/spec/controller/comments_spec.rb +54 -0
- data/spec/controller/items_spec.rb +45 -0
- data/spec/models/attachments_spec.rb +24 -0
- data/spec/models/attachments_spec/a.txt +1 -0
- data/spec/models/attachments_uploader_helper_spec.rb +108 -0
- data/spec/models/attachments_uploader_helper_spec/v1/a.txt +1 -0
- data/spec/models/attachments_uploader_helper_spec/v1/b.txt +1 -0
- data/spec/models/attachments_uploader_helper_spec/v2/a.txt +1 -0
- data/spec/models/authorization_spec.rb +77 -0
- data/spec/models/authorized_object_spec.rb +254 -0
- data/spec/models/comments_spec.rb +1 -0
- data/spec/models/item_spec.rb +51 -0
- data/spec/models/role_spec.rb +17 -0
- data/spec/models/tags_spec.rb +44 -0
- data/spec/models/uploader_spec.rb +37 -0
- data/spec/models/uploader_spec//321/204/320/260/320/270/314/206/320/273 /321/201 /320/277/321/200/320/276/320/261/320/265/320/273/320/260/320/274/320/270.txt" +1 -0
- data/spec/mongoid/basic_spec.rb +36 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/spec_helper/controller.rb +9 -0
- data/spec/spec_helper/factories.rb +24 -0
- data/spec/spec_helper/user.rb +17 -0
- data/spec/utils/text_utils_spec.rb +280 -0
- metadata +232 -0
data/lib/kit/spec.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rad'
|
2
|
+
|
3
|
+
require 'rad/spec'
|
4
|
+
|
5
|
+
require 'mongoid_misc'
|
6
|
+
require 'mongoid_misc/spec'
|
7
|
+
require 'carrierwave_ext/spec'
|
8
|
+
|
9
|
+
|
10
|
+
#
|
11
|
+
# SaaS
|
12
|
+
#
|
13
|
+
unless ENV['saas'] == 'false'
|
14
|
+
begin
|
15
|
+
require 'saas/spec'
|
16
|
+
rescue LoadError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
#
|
22
|
+
# User
|
23
|
+
#
|
24
|
+
rad.register :user
|
25
|
+
rspec do
|
26
|
+
def login_as user
|
27
|
+
rad.user = user
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.login_as name, options = {}
|
31
|
+
before do
|
32
|
+
@user = Factory.create name, options
|
33
|
+
login_as @user
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
#
|
40
|
+
# CarrierWave
|
41
|
+
#
|
42
|
+
module Rad::CarrierWaveSpecHelper
|
43
|
+
def with_files
|
44
|
+
before do
|
45
|
+
rad.config.fs_path = CarrierWaveExtSpecHelper::TEST_PATH
|
46
|
+
rad.config.fs_cache_path = CarrierWaveExtSpecHelper::TEST_CACHE_PATH
|
47
|
+
|
48
|
+
Models::FileUploader.storage :file
|
49
|
+
end
|
50
|
+
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
rspec.extend Rad::CarrierWaveSpecHelper
|
55
|
+
|
56
|
+
|
57
|
+
#
|
58
|
+
# Micelaneous
|
59
|
+
#
|
60
|
+
rspec do
|
61
|
+
alias_method :call, :wcall
|
62
|
+
alias_method :pcall, :post_wcall
|
63
|
+
alias_method :set_call, :set_wcall
|
64
|
+
|
65
|
+
class << self
|
66
|
+
def with_models
|
67
|
+
with_mongoid
|
68
|
+
rad.extension :with_models, self
|
69
|
+
end
|
70
|
+
|
71
|
+
def with_controllers
|
72
|
+
with_models
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
require 'kit/spec/items_controller_crud'
|
@@ -0,0 +1,64 @@
|
|
1
|
+
shared_examples_for "Items Controller CRUD" do
|
2
|
+
before do
|
3
|
+
@controller_class.should be_present
|
4
|
+
@model_class ||= @controller_class.model_class
|
5
|
+
@model_name ||= @model_class.alias.underscore
|
6
|
+
end
|
7
|
+
|
8
|
+
%w{html json}.each do |format|
|
9
|
+
it "show :#{format}" do
|
10
|
+
@item = Factory.create @model_name
|
11
|
+
call :show, id: @item.to_param, format: format
|
12
|
+
response.should be_ok
|
13
|
+
response.body.should include(@item.name)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "all :#{format}" do
|
17
|
+
@item = Factory.create @model_name
|
18
|
+
call :all, format: format
|
19
|
+
response.should be_ok
|
20
|
+
response.body.should include(@item.name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "edit :js" do
|
25
|
+
item = Factory.create @model_name
|
26
|
+
call :edit, id: item.to_param, format: 'js'
|
27
|
+
response.should be_ok
|
28
|
+
end
|
29
|
+
|
30
|
+
%w{js json}.each do |format|
|
31
|
+
it "new :#{format}" do
|
32
|
+
call :new, format: format
|
33
|
+
response.should be_ok
|
34
|
+
end
|
35
|
+
|
36
|
+
it "create :#{format}" do
|
37
|
+
item_attributes = Factory.attributes_for @model_name
|
38
|
+
pcall :create, model: item_attributes, format: format
|
39
|
+
|
40
|
+
(format == 'js') ? response.body.should(include('window.location')) : response.should(be_ok)
|
41
|
+
@model_class.count.should == 1
|
42
|
+
item = @model_class.first
|
43
|
+
item.name.should == item_attributes[:name]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "update :#{format}" do
|
47
|
+
item = Factory.create @model_name
|
48
|
+
pcall :update, id: item.to_param, model: {name: 'new_name'}, format: format
|
49
|
+
|
50
|
+
response.should be_ok
|
51
|
+
response.body.should =~ /new_name|reload/ if format == 'js'
|
52
|
+
|
53
|
+
item.reload
|
54
|
+
item.name.should == 'new_name'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "destroy :#{format}" do
|
58
|
+
item = Factory.create @model_name
|
59
|
+
pcall :destroy, id: item.to_param, format: format
|
60
|
+
response.should be_ok
|
61
|
+
@model_class.count.should == 0
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/kit/support.rb
ADDED
data/lib/kit/tasks.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rake_ext'
|
2
|
+
|
3
|
+
require 'rad_ext/tasks'
|
4
|
+
require 'rad/assets/tasks'
|
5
|
+
require 'mongo_migration/tasks'
|
6
|
+
|
7
|
+
namespace :db do
|
8
|
+
desc "Internal task to prepare migration environment"
|
9
|
+
task migration_evnironment: :environment do
|
10
|
+
require 'mongo_migration'
|
11
|
+
require 'mongo_migration/adapters/mongoid'
|
12
|
+
|
13
|
+
adapter = Mongo::Migration::Mongoid.new
|
14
|
+
Mongo.migration = Mongo::Migration.new adapter
|
15
|
+
|
16
|
+
Dir["#{rad.runtime_path}/db/**/*.rb"].each{|f| require f.sub(/\.rb$/, '')}
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'bluecloth'
|
5
|
+
require 'sanitize'
|
6
|
+
|
7
|
+
module Rad::TextUtils; end
|
8
|
+
|
9
|
+
%w(
|
10
|
+
processor
|
11
|
+
|
12
|
+
github_flavoured_markdown
|
13
|
+
image_box
|
14
|
+
custom_markdown
|
15
|
+
urls
|
16
|
+
tag_shortcuts
|
17
|
+
html_sanitizer
|
18
|
+
ensure_utf
|
19
|
+
truncate
|
20
|
+
code_highlighter
|
21
|
+
truncator
|
22
|
+
|
23
|
+
markup
|
24
|
+
).each{|f| require "kit/text_utils/#{f}"}
|
25
|
+
|
26
|
+
module Rad::TextUtils
|
27
|
+
class << self
|
28
|
+
def markup text
|
29
|
+
Rad::TextUtils::Markup.new.process text, {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def random_string length = 3
|
33
|
+
@digits ||= ('a'..'z').to_a + (0..9).to_a
|
34
|
+
(0..(length-1)).map{@digits[rand(@digits.size)]}.join
|
35
|
+
end
|
36
|
+
|
37
|
+
def truncate str_or_html, length
|
38
|
+
Rad::TextUtils::Truncator.new(nil, length).process str_or_html, {}
|
39
|
+
# str_or_html = HtmlSanitizer.new.process str_or_html, {}
|
40
|
+
# Truncator.new(length).process str_or_html
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Rad::TextUtils::CodeHighlighter < Rad::TextUtils::Processor
|
2
|
+
|
3
|
+
# highlights code inside of <code lang/language='java'> ... code ... </code>
|
4
|
+
def process html, env
|
5
|
+
require 'coderay'
|
6
|
+
require 'nokogiri'
|
7
|
+
|
8
|
+
snippets, unique_id = {}, 0
|
9
|
+
|
10
|
+
# Highlighting
|
11
|
+
html = html.gsub /(<code.*?>)(.+?)(<\/code\s*>)/im do
|
12
|
+
begin
|
13
|
+
node = Nokogiri::HTML($1 + $3).css('code').first
|
14
|
+
language = node.attributes['lang'].try(:value) || node.attributes['language'].try(:value)
|
15
|
+
code = $2
|
16
|
+
|
17
|
+
if language.present? and code.present?
|
18
|
+
attributes = []
|
19
|
+
node.attributes.each do |name, value|
|
20
|
+
attributes << %(#{name}="#{value.value}")
|
21
|
+
end
|
22
|
+
|
23
|
+
code = CodeRay.scan(code, language.downcase.to_sym).div :css => :class
|
24
|
+
|
25
|
+
snippet = "<code #{attributes.join(' ')}>\n#{code}\n</code>"
|
26
|
+
|
27
|
+
# adding prefix 'hl_' to all CodeRay classes
|
28
|
+
node = Nokogiri::HTML(snippet).css('code').first
|
29
|
+
node.css("*").each do |e|
|
30
|
+
classes = e.attribute 'class'
|
31
|
+
if classes.present? and classes.value.present?
|
32
|
+
classes = classes.value.strip.split(/\s+/).collect{|c| "hl_#{c}"}.join(' ')
|
33
|
+
e['class'] = classes
|
34
|
+
end
|
35
|
+
end
|
36
|
+
snippet = node.to_s
|
37
|
+
end
|
38
|
+
# rescue StandardError => e
|
39
|
+
# rad.logger.warn "CodeHighlighter: #{e.message}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# temporarilly removing all highlighted code from html to prevent it's beed damaged by next processors
|
43
|
+
unique_id += 1
|
44
|
+
id = "CODE#{unique_id}CODE"
|
45
|
+
snippets[id] = snippet
|
46
|
+
id
|
47
|
+
end
|
48
|
+
|
49
|
+
html = call_next html, env
|
50
|
+
|
51
|
+
# inserting highlighted code back to html
|
52
|
+
html = html.gsub /(CODE[0-9]+CODE)/ do |id|
|
53
|
+
snippets[id]
|
54
|
+
end
|
55
|
+
|
56
|
+
html
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class Rad::TextUtils::CustomMarkdown < Rad::TextUtils::Processor
|
2
|
+
def process markdown, env
|
3
|
+
markdown = github_flavoured_markdown markdown
|
4
|
+
markdown = image_box markdown
|
5
|
+
text = custom_markdown markdown
|
6
|
+
|
7
|
+
call_next text, env
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
def custom_markdown markdown
|
12
|
+
# BlueCloth doesn't apply inside of HTML tags, so we temporarilly replacing it
|
13
|
+
markdown = markdown.gsub('<', 'HTML_BEGIN').gsub('>', 'HTML_END')
|
14
|
+
|
15
|
+
markdown = markdown.gsub(" \n", "<br/>\n")
|
16
|
+
|
17
|
+
text = BlueCloth.new(markdown).to_html
|
18
|
+
|
19
|
+
text = text.gsub(/\A<.+?>/){"#{$&} "}.gsub(/<\/.+?>\Z/){" #{$&}"}
|
20
|
+
|
21
|
+
text = text.gsub('HTML_BEGIN', '<').gsub('HTML_END', '>')
|
22
|
+
|
23
|
+
text.gsub(/[\n]+/, "\n")
|
24
|
+
end
|
25
|
+
|
26
|
+
def github_flavoured_markdown markdown
|
27
|
+
# Extract pre blocks
|
28
|
+
extractions = {}
|
29
|
+
markdown.gsub!(%r{<pre>.*?</pre>}m) do |match|
|
30
|
+
md5 = Digest::MD5.hexdigest(match)
|
31
|
+
extractions[md5] = match
|
32
|
+
"{gfm-extraction-#{md5}}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# prevent foo_bar_baz from ending up with an italic word in the middle
|
36
|
+
markdown.gsub!(/(^(?! {4}|\t)\w+_\w+_\w[\w_]*)/) do |x|
|
37
|
+
x.gsub('_', '\_') if x.split('').sort.to_s[0..1] == '__'
|
38
|
+
end
|
39
|
+
|
40
|
+
# in very clear cases, let newlines become <br /> tags
|
41
|
+
markdown.gsub!(/^[\w\<\!][^\n]*\n+/) do |x|
|
42
|
+
if x =~ /\>[\n\r]*/
|
43
|
+
x
|
44
|
+
else
|
45
|
+
x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Insert pre block extractions
|
50
|
+
markdown.gsub!(/\{gfm-extraction-([0-9a-f]{32})\}/) do
|
51
|
+
"\n\n" + extractions[$1]
|
52
|
+
end
|
53
|
+
|
54
|
+
markdown
|
55
|
+
end
|
56
|
+
|
57
|
+
# !![img] => [![img_thumb]][img]
|
58
|
+
def image_box markdown
|
59
|
+
img_urls = {}
|
60
|
+
markdown = markdown.gsub(/!!\[(.+?)\]/) do
|
61
|
+
img_key = $1 || ''
|
62
|
+
if url = markdown.scan(/\[#{img_key}\]:\s*([^\s]+)$/).first.try(:first)
|
63
|
+
unless url =~ /\.[^\.]+\.[^\.]+$/ # image.png
|
64
|
+
thumb_img_key = "#{img_key}_thumb"
|
65
|
+
|
66
|
+
# building url with thumb (foo.png => foo.thumb.png)
|
67
|
+
img_urls[thumb_img_key] = url.sub(/\.([^\.]+)$/){".thumb.#{$1}"}
|
68
|
+
|
69
|
+
"[![][#{thumb_img_key}]][#{img_key}]"
|
70
|
+
else # image.(icon|thumb|...).png
|
71
|
+
img_key_full = "#{img_key}_full"
|
72
|
+
|
73
|
+
# building url with thumb (foo.png => foo.thumb.png)
|
74
|
+
img_urls[img_key_full] = url.sub(/\.([^\.]+)\.([^\.]+)$/){".#{$2}"}
|
75
|
+
|
76
|
+
"[![][#{img_key}]][#{img_key_full}]"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
$& || ''
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
unless img_urls.blank?
|
84
|
+
markdown << "\n"
|
85
|
+
markdown << img_urls.to_a.collect{|k, v| "[#{k}]: #{v}"}.join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
markdown
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Rad::TextUtils::EnsureUtf < Rad::TextUtils::Processor
|
2
|
+
def process data, env
|
3
|
+
data = call_next data, env
|
4
|
+
|
5
|
+
# Escape all non-word unicode symbols, otherwise it will raise error when converting to BSON
|
6
|
+
Iconv.conv('UTF-8//IGNORE//TRANSLIT', 'UTF-8', data)
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Rad::TextUtils::GithubFlavouredMarkdown < Rad::TextUtils::Processor
|
2
|
+
def process markdown, env
|
3
|
+
# Extract pre blocks
|
4
|
+
extractions = {}
|
5
|
+
markdown.gsub!(%r{<pre>.*?</pre>}m) do |match|
|
6
|
+
md5 = Digest::MD5.hexdigest(match)
|
7
|
+
extractions[md5] = match
|
8
|
+
"{gfm-extraction-#{md5}}"
|
9
|
+
end
|
10
|
+
|
11
|
+
# prevent foo_bar_baz from ending up with an italic word in the middle
|
12
|
+
markdown.gsub!(/(^(?! {4}|\t)\w+_\w+_\w[\w_]*)/) do |x|
|
13
|
+
x.gsub('_', '\_') if x.split('').sort.to_s[0..1] == '__'
|
14
|
+
end
|
15
|
+
|
16
|
+
# in very clear cases, let newlines become <br /> tags
|
17
|
+
markdown.gsub!(/^[\w\<\!][^\n]*\n+/) do |x|
|
18
|
+
if x =~ /\>[\n\r]*/
|
19
|
+
x
|
20
|
+
else
|
21
|
+
x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Insert pre block extractions
|
26
|
+
markdown.gsub!(/\{gfm-extraction-([0-9a-f]{32})\}/) do
|
27
|
+
"\n\n" + extractions[$1]
|
28
|
+
end
|
29
|
+
|
30
|
+
call_next markdown, env
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class Rad::TextUtils::HtmlSanitizer < Rad::TextUtils::Processor
|
2
|
+
RELAXED = {
|
3
|
+
elements: [
|
4
|
+
'a', 'b', 'blockquote', 'br', 'caption', 'cite', 'code', 'col',
|
5
|
+
'colgroup', 'dd', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
6
|
+
'i', 'img', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong',
|
7
|
+
'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'u',
|
8
|
+
'ul', 'div', 'font', 'span'],
|
9
|
+
|
10
|
+
attributes: {
|
11
|
+
:all => ['class', 'style'],
|
12
|
+
'a' => ['href', 'title', 'rel'],
|
13
|
+
'blockquote' => ['cite'],
|
14
|
+
'col' => ['span', 'width'],
|
15
|
+
'colgroup' => ['span', 'width'],
|
16
|
+
'img' => ['align', 'alt', 'height', 'src', 'title', 'width'],
|
17
|
+
'ol' => ['start', 'type'],
|
18
|
+
'q' => ['cite'],
|
19
|
+
'table' => ['summary', 'width'],
|
20
|
+
'td' => ['abbr', 'axis', 'colspan', 'rowspan', 'width'],
|
21
|
+
'th' => ['abbr', 'axis', 'colspan', 'rowspan', 'scope', 'width'],
|
22
|
+
'ul' => ['type'],
|
23
|
+
'code' => ['lang', 'language']
|
24
|
+
},
|
25
|
+
|
26
|
+
protocols: {
|
27
|
+
'a' => {'href' => ['ftp', 'http', 'https', 'mailto', :relative]},
|
28
|
+
'blockquote' => {'cite' => ['http', 'https', :relative]},
|
29
|
+
'img' => {'src' => ['http', 'https', :relative]},
|
30
|
+
'q' => {'cite' => ['http', 'https', :relative]}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
VIDEO_URLS = [
|
35
|
+
/^http:\/\/(?:www\.)?youtube\.com\/v\//,
|
36
|
+
]
|
37
|
+
|
38
|
+
EMBEDDED_VIDEO = lambda do |env|
|
39
|
+
node = env[:node]
|
40
|
+
node_name = node.name.to_s.downcase
|
41
|
+
parent = node.parent
|
42
|
+
|
43
|
+
# Since the transformer receives the deepest nodes first, we look for a
|
44
|
+
# <param> element or an <embed> element whose parent is an <object>.
|
45
|
+
return nil unless (node_name == 'param' || node_name == 'embed') && parent.name.to_s.downcase == 'object'
|
46
|
+
|
47
|
+
if node_name == 'param'
|
48
|
+
# Quick XPath search to find the <param> node that contains the video URL.
|
49
|
+
return nil unless movie_node = parent.search('param[@name="movie"]')[0]
|
50
|
+
url = movie_node['value']
|
51
|
+
else
|
52
|
+
# Since this is an <embed>, the video URL is in the "src" attribute. No
|
53
|
+
# extra work needed.
|
54
|
+
url = node['src']
|
55
|
+
end
|
56
|
+
|
57
|
+
# # Verify that the video URL is actually a valid YouTube video URL.
|
58
|
+
return nil unless VIDEO_URLS.any?{|t| url =~ t}
|
59
|
+
|
60
|
+
# # We're now certain that this is a YouTube embed, but we still need to run
|
61
|
+
# # it through a special Sanitize step to ensure that no unwanted elements or
|
62
|
+
# # attributes that don't belong in a YouTube embed can sneak in.
|
63
|
+
Sanitize.clean_node!(parent, {
|
64
|
+
:elements => ['embed', 'object', 'param'],
|
65
|
+
attributes: {
|
66
|
+
'embed' => ['allowfullscreen', 'allowscriptaccess', 'height', 'src', 'type', 'width'],
|
67
|
+
'object' => ['height', 'width'],
|
68
|
+
'param' => ['name', 'value']
|
69
|
+
}
|
70
|
+
})
|
71
|
+
|
72
|
+
# Now that we're sure that this is a valid YouTube embed and that there are
|
73
|
+
# no unwanted elements or attributes hidden inside it, we can tell Sanitize
|
74
|
+
# to whitelist the current node (<param> or <embed>) and its parent
|
75
|
+
# (<object>).
|
76
|
+
{:whitelist_nodes => [node, parent]}
|
77
|
+
end
|
78
|
+
|
79
|
+
def process html, env
|
80
|
+
html = call_next html, env
|
81
|
+
|
82
|
+
Sanitize.clean(html, RELAXED.merge(
|
83
|
+
transformers: [EMBEDDED_VIDEO],
|
84
|
+
:add_attributes => {
|
85
|
+
all: [:class]
|
86
|
+
}
|
87
|
+
))
|
88
|
+
end
|
89
|
+
end
|