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