gluttonberg-core 2.5.5 → 2.5.6
Sign up to get free protection for your applications and to get access to all the features.
- data/app/assets/javascripts/gb_application.js +34 -0
- data/app/assets/stylesheets/gb_admin-override.sass +17 -1
- data/app/controllers/gluttonberg/admin/asset_library/assets_controller.rb +4 -2
- data/app/controllers/gluttonberg/admin/content/articles_controller.rb +22 -22
- data/app/controllers/gluttonberg/admin/content/comments_controller.rb +80 -23
- data/app/controllers/gluttonberg/admin/main_controller.rb +1 -1
- data/app/controllers/gluttonberg/public/comments_controller.rb +9 -8
- data/app/helpers/gluttonberg/admin.rb +3 -3
- data/app/helpers/gluttonberg/asset_library.rb +14 -11
- data/app/models/gluttonberg/asset.rb +8 -6
- data/app/models/gluttonberg/comment.rb +57 -3
- data/app/models/gluttonberg/comment_subscription.rb +2 -0
- data/app/models/gluttonberg/setting.rb +11 -5
- data/app/views/gluttonberg/admin/asset_library/assets/_browser_root.html.haml +20 -3
- data/app/views/gluttonberg/admin/asset_library/assets/search.json.haml +1 -1
- data/app/views/gluttonberg/admin/content/comments/index.html.haml +36 -10
- data/app/views/gluttonberg/admin/content/main/_sidebar.html.haml +6 -3
- data/app/views/gluttonberg/admin/main/index.html.haml +4 -4
- data/app/views/gluttonberg/admin/settings/generic_settings/index.html.haml +11 -7
- data/config/routes.rb +10 -0
- data/db/migrate/20130201025800_spam_flag_for_comments.rb +6 -0
- data/lib/engine.rb +3 -2
- data/lib/generators/gluttonberg/installer/installer_generator.rb +33 -32
- data/lib/gluttonberg/content/block.rb +2 -0
- data/lib/gluttonberg/content/clean_html.rb +16 -14
- data/lib/gluttonberg/content/despamilator/conf/unusual_characters.txt +6674 -0
- data/lib/gluttonberg/content/despamilator/filter/emails.rb +49 -0
- data/lib/gluttonberg/content/despamilator/filter/gtubs_test_filter.rb +25 -0
- data/lib/gluttonberg/content/despamilator/filter/html_tags.rb +134 -0
- data/lib/gluttonberg/content/despamilator/filter/ip_address_url.rb +27 -0
- data/lib/gluttonberg/content/despamilator/filter/long_words.rb +29 -0
- data/lib/gluttonberg/content/despamilator/filter/mixed_case.rb +25 -0
- data/lib/gluttonberg/content/despamilator/filter/naughty_words.rb +80 -0
- data/lib/gluttonberg/content/despamilator/filter/no_vowels.rb +28 -0
- data/lib/gluttonberg/content/despamilator/filter/numbers_and_words.rb +55 -0
- data/lib/gluttonberg/content/despamilator/filter/obfuscated_urls.rb +45 -0
- data/lib/gluttonberg/content/despamilator/filter/prices.rb +23 -0
- data/lib/gluttonberg/content/despamilator/filter/script_tag.rb +25 -0
- data/lib/gluttonberg/content/despamilator/filter/shouting.rb +38 -0
- data/lib/gluttonberg/content/despamilator/filter/spammy_tlds.rb +26 -0
- data/lib/gluttonberg/content/despamilator/filter/square_brackets.rb +27 -0
- data/lib/gluttonberg/content/despamilator/filter/trailing_number.rb +25 -0
- data/lib/gluttonberg/content/despamilator/filter/unusual_characters.rb +51 -0
- data/lib/gluttonberg/content/despamilator/filter/urls.rb +45 -0
- data/lib/gluttonberg/content/despamilator/filter/very_long_domain_name.rb +31 -0
- data/lib/gluttonberg/content/despamilator/filter/weird_punctuation.rb +48 -0
- data/lib/gluttonberg/content/despamilator/filter.rb +57 -0
- data/lib/gluttonberg/content/despamilator/subject/text.rb +36 -0
- data/lib/gluttonberg/content/despamilator/subject.rb +34 -0
- data/lib/gluttonberg/content/despamilator/version.rb +7 -0
- data/lib/gluttonberg/content/despamilator.rb +79 -0
- data/lib/gluttonberg/content.rb +12 -11
- data/lib/gluttonberg/library/attachment_mixin.rb +52 -269
- data/lib/gluttonberg/library/config/image_sizes.rb +61 -0
- data/lib/gluttonberg/library/config.rb +10 -0
- data/lib/gluttonberg/library/processor/audio.rb +42 -0
- data/lib/gluttonberg/library/processor/image.rb +134 -0
- data/lib/gluttonberg/library/processor.rb +11 -0
- data/lib/gluttonberg/library/storage/filesystem.rb +76 -0
- data/lib/gluttonberg/library/storage/s3.rb +196 -0
- data/lib/gluttonberg/library/storage.rb +11 -0
- data/lib/gluttonberg/library.rb +87 -86
- data/lib/gluttonberg/record_history.rb +14 -15
- data/lib/gluttonberg/tasks/asset.rake +25 -3
- data/lib/gluttonberg/version.rb +1 -1
- metadata +53 -2
@@ -0,0 +1,51 @@
|
|
1
|
+
module Gluttonberg
|
2
|
+
module Content
|
3
|
+
require 'despamilator/filter'
|
4
|
+
|
5
|
+
module DespamilatorFilter
|
6
|
+
|
7
|
+
class UnusualCharacters < Despamilator::Filter
|
8
|
+
|
9
|
+
def name
|
10
|
+
'Unusual Characters'
|
11
|
+
end
|
12
|
+
|
13
|
+
def description
|
14
|
+
'Detects and scores each occurrence of an unusual 2 or 3 character combination'
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse subject
|
18
|
+
initialize_combos
|
19
|
+
tokenize(subject.text.without_uris).each do |token|
|
20
|
+
subject.register_match!({:score => 0.05, :filter => self}) if @@combos[token.to_sym]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def tokenize text
|
27
|
+
tokens = []
|
28
|
+
text.downcase.split(/[^a-z]/).each do |word|
|
29
|
+
word.chars.each_with_index do |c, i|
|
30
|
+
substr = word[i,i+3]
|
31
|
+
tokens << substr.to_sym if substr.length == 3
|
32
|
+
tokens << substr[0,2].to_sym if substr.length > 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
tokens
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize_combos
|
39
|
+
@@combos ||= {}
|
40
|
+
return @@combos unless @@combos.empty?
|
41
|
+
|
42
|
+
File.open(File.join(File.dirname(__FILE__), %w{.. conf unusual_characters.txt}), 'r').each do |line|
|
43
|
+
@@combos[line.strip.to_sym] = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end #Content
|
51
|
+
end #Gluttonberg
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Gluttonberg
|
2
|
+
module Content
|
3
|
+
require 'despamilator/filter'
|
4
|
+
|
5
|
+
module DespamilatorFilter
|
6
|
+
|
7
|
+
class URLs < Despamilator::Filter
|
8
|
+
|
9
|
+
def name
|
10
|
+
'URLs'
|
11
|
+
end
|
12
|
+
|
13
|
+
def description
|
14
|
+
'Detects each url in a string'
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse subject
|
18
|
+
text = subject.text.downcase.gsub(/http:\/\/\d+\.\d+\.\d+\.\d+/, '')
|
19
|
+
matches = text.count(/https?:\/\//)
|
20
|
+
comment_number_of_urls_allowed = Gluttonberg::Setting.get_setting("comment_number_of_urls_allowed")
|
21
|
+
score_for_one_url = 0.4
|
22
|
+
if !comment_number_of_urls_allowed.blank? && comment_number_of_urls_allowed.to_i > 0
|
23
|
+
comment_number_of_urls_allowed = comment_number_of_urls_allowed.to_i
|
24
|
+
score_for_one_url = 1.0 / comment_number_of_urls_allowed.to_i
|
25
|
+
end
|
26
|
+
1.upto(matches > 2 ? 2 : matches) do
|
27
|
+
subject.register_match!({:score => score_for_one_url, :filter => self})
|
28
|
+
end
|
29
|
+
|
30
|
+
comment_email_as_spam = Gluttonberg::Setting.get_setting("comment_email_as_spam")
|
31
|
+
if comment_email_as_spam == "Yes"
|
32
|
+
text_temp = text.strip
|
33
|
+
extracted_urls = URI.extract(text_temp)
|
34
|
+
subject.register_match!({
|
35
|
+
:score => 1.0, :filter => self
|
36
|
+
}) if extracted_urls.length > 0 && extracted_urls[0] == text_temp
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end #Content
|
45
|
+
end #Gluttonberg
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Gluttonberg
|
2
|
+
module Content
|
3
|
+
require 'despamilator/filter'
|
4
|
+
require 'domainatrix'
|
5
|
+
|
6
|
+
module DespamilatorFilter
|
7
|
+
|
8
|
+
class VeryLongDomainName < Despamilator::Filter
|
9
|
+
|
10
|
+
def name
|
11
|
+
'Very Long Domain Name'
|
12
|
+
end
|
13
|
+
|
14
|
+
def description
|
15
|
+
'Detects unusually long domain names.'
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse subject
|
19
|
+
subject.text.scan(URI.regexp).each do |url_parts|
|
20
|
+
url_parts.compact!
|
21
|
+
next if !url_parts[1] or url_parts[1] !~ /(\w|-){5,}\.\w{2,5}/
|
22
|
+
url = Domainatrix.parse('http://' + url_parts[1])
|
23
|
+
subject.register_match!({:score => 0.4, :filter => self}) if url.domain.length > 20
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end #Content
|
31
|
+
end #Gluttonberg
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Gluttonberg
|
2
|
+
module Content
|
3
|
+
require 'despamilator/filter'
|
4
|
+
|
5
|
+
module DespamilatorFilter
|
6
|
+
|
7
|
+
class WeirdPunctuation < Despamilator::Filter
|
8
|
+
|
9
|
+
def name
|
10
|
+
'Weird Punctuation'
|
11
|
+
end
|
12
|
+
|
13
|
+
def description
|
14
|
+
'Detects unusual use of punctuation.'
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse subject
|
18
|
+
text = subject.text.without_uris.downcase
|
19
|
+
|
20
|
+
text.gsub!(/\w&\w/, 'xx')
|
21
|
+
text.gsub!(/[a-z](!|\?)(\s|$)/, 'x')
|
22
|
+
text.gsub!(/(?:#{punctuation}){20,}/, '')
|
23
|
+
matches = text.remove_and_count!(/(?:\W|\s|^)(#{punctuation})/)
|
24
|
+
matches += text.remove_and_count!(/\w,\w/)
|
25
|
+
matches += text.remove_and_count!(/\w\w\.\w/)
|
26
|
+
matches += text.remove_and_count!(/\w\.\w\w/)
|
27
|
+
matches += text.remove_and_count!(/(#{punctuation})(#{punctuation})/)
|
28
|
+
matches += text.remove_and_count!(/(#{punctuation})$/)
|
29
|
+
matches += text.remove_and_count!(/(?:\W|\s|^)\d+(#{punctuation})/)
|
30
|
+
|
31
|
+
subject.register_match!({:score => 0.03 * matches, :filter => self}) if matches > 0
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def punctuation
|
37
|
+
@punctuation ||= %w{~ ` ! @ # $ % ^ & * _ - + = , / ? | \\ : ;}.map do |punctuation_character|
|
38
|
+
Regexp.escape(punctuation_character)
|
39
|
+
end.join('|')
|
40
|
+
|
41
|
+
@punctuation
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end #Content
|
48
|
+
end #Gluttonberg
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Gluttonberg
|
2
|
+
module Content
|
3
|
+
class Despamilator
|
4
|
+
|
5
|
+
#This class is the base class of all the despamilator filters.
|
6
|
+
#
|
7
|
+
#== EXAMPLE:
|
8
|
+
#
|
9
|
+
#This example is to detect the letter "a". Put the code in
|
10
|
+
#lib/despamilator/filter/detect_letter_a.rb:
|
11
|
+
#
|
12
|
+
# require 'despamilator/filter_base'
|
13
|
+
#
|
14
|
+
# module DespamilatorFilter
|
15
|
+
#
|
16
|
+
# class DetectLetterA < Despamilator::FilterBase
|
17
|
+
#
|
18
|
+
# def name
|
19
|
+
# 'Detecting the letter A'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def description
|
23
|
+
# 'Detects the letter "a" in a string for no reason other than a demo'
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def parse text
|
27
|
+
# if text.downcase.scan(/a/)
|
28
|
+
# # add 0.1 to the score of the text
|
29
|
+
# self.append_score = 0.1
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
|
34
|
+
class Filter
|
35
|
+
|
36
|
+
# The nice description of the filter. Usually no more than a sentence.
|
37
|
+
|
38
|
+
def description
|
39
|
+
raise "No description defined for #{self.class}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# This method parses some text. The score is assigned to the same instance.
|
43
|
+
|
44
|
+
def parse text
|
45
|
+
raise "No parser defined for #{self.class}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# The one or two word name for the filter.
|
49
|
+
|
50
|
+
def name
|
51
|
+
raise "No name defined for #{self.class}"
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end #content
|
57
|
+
end #Gluttonberg
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Gluttonberg
|
2
|
+
module Content
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
class Despamilator
|
6
|
+
class Subject
|
7
|
+
class Text < String
|
8
|
+
|
9
|
+
def initialize text
|
10
|
+
super text
|
11
|
+
freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def without_uris
|
15
|
+
gsub(/\b(?:https?|mailto|ftp):.+?(\s|$)/i, '')
|
16
|
+
end
|
17
|
+
|
18
|
+
def words
|
19
|
+
split(/\W+/)
|
20
|
+
end
|
21
|
+
|
22
|
+
def count pattern
|
23
|
+
scan(pattern).flatten.compact.length
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove_and_count! pattern
|
27
|
+
count = count(pattern)
|
28
|
+
gsub!(pattern, '')
|
29
|
+
count
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end #Content
|
36
|
+
end #Gluttonberg
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Gluttonberg
|
2
|
+
module Content
|
3
|
+
require 'despamilator/subject/text'
|
4
|
+
|
5
|
+
class Despamilator
|
6
|
+
class Subject
|
7
|
+
attr_reader :score, :text
|
8
|
+
|
9
|
+
def initialize text
|
10
|
+
@score = 0.0
|
11
|
+
@matches = {}
|
12
|
+
@text = Despamilator::Subject::Text.new(text)
|
13
|
+
end
|
14
|
+
|
15
|
+
def register_match! details
|
16
|
+
@score += details[:score] || raise('A score must be supplied')
|
17
|
+
filter = details[:filter] || raise('A filter must be supplied')
|
18
|
+
|
19
|
+
@matches[filter] ||= 0.0
|
20
|
+
@matches[filter] += details[:score]
|
21
|
+
end
|
22
|
+
|
23
|
+
def matches
|
24
|
+
@matches.map do |filter, score|
|
25
|
+
{:filter => filter, :score => score}
|
26
|
+
end.sort do |a, b|
|
27
|
+
b[:score] <=> a[:score]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end #Content
|
34
|
+
end #Gluttonberg
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Gluttonberg
|
2
|
+
module Content
|
3
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
4
|
+
require 'despamilator/filter'
|
5
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'despamilator', 'filter', '*.rb')).each do |filter_file|
|
6
|
+
require filter_file
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'despamilator/subject'
|
10
|
+
require 'ostruct'
|
11
|
+
|
12
|
+
#== SYNOPSIS:
|
13
|
+
#
|
14
|
+
# require 'despamilator'
|
15
|
+
#
|
16
|
+
# # some time later...
|
17
|
+
#
|
18
|
+
# dspam = Despamilator.new('some text with an <h2> tag qthhg')
|
19
|
+
#
|
20
|
+
# dspam.score #=> the total score for this string (1 is normally my threshold).
|
21
|
+
# dspam.matches #=> array of hashes containing matching filters and their score.
|
22
|
+
|
23
|
+
class Despamilator
|
24
|
+
|
25
|
+
# Constructor. Takes the text you which to parse and score.
|
26
|
+
|
27
|
+
def initialize text
|
28
|
+
@subject = Despamilator::Subject.new text
|
29
|
+
run_filters @subject
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the total score as a Float.
|
33
|
+
|
34
|
+
def score
|
35
|
+
@subject.score
|
36
|
+
end
|
37
|
+
|
38
|
+
def matched_by
|
39
|
+
warn 'Despamilator.matched_by is deprecated, please use Despamilator.matches by 2011-12-31.'
|
40
|
+
|
41
|
+
matches.map do |match|
|
42
|
+
filter = match[:filter]
|
43
|
+
|
44
|
+
OpenStruct.new(
|
45
|
+
:name => filter.name,
|
46
|
+
:description => filter.description,
|
47
|
+
:score => match[:score]
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns an array of scores and filters that have matched and contributed to the score.
|
53
|
+
# Each element is a a child of the Despamilator::FilterBase class.
|
54
|
+
|
55
|
+
def matches
|
56
|
+
@subject.matches
|
57
|
+
end
|
58
|
+
|
59
|
+
# Generic Test for Unsolicited Bulk Submissions. Similar to SpamAssassin's GTUBE.
|
60
|
+
# A string that will result in a spam score of at least 100. Handy for testing.
|
61
|
+
|
62
|
+
def self.gtubs_test_string
|
63
|
+
'89913b8a065b7092721fe995877e097681683af9d3ab767146d5d6fd050fc0bda7ab99f4232d94a1'
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def run_filters subject
|
69
|
+
filter_namespace = Gluttonberg::Content.const_get('DespamilatorFilter')
|
70
|
+
|
71
|
+
filter_namespace.constants.each do |filter_class|
|
72
|
+
filter = filter_namespace.const_get(filter_class).new
|
73
|
+
filter.parse(subject)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end #content
|
79
|
+
end # Gluttonberg
|
data/lib/gluttonberg/content.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
content = Pathname(__FILE__).dirname.expand_path
|
2
2
|
|
3
|
-
require File.join(content,
|
3
|
+
require File.join(content, "content", "slug_management")
|
4
4
|
require File.join(content, "content", "block")
|
5
5
|
require File.join(content, "content", "block_localization")
|
6
6
|
require File.join(content, "content", "localization")
|
@@ -8,9 +8,10 @@ require File.join(content, "content", "publishable")
|
|
8
8
|
require File.join(content, "content", "versioning")
|
9
9
|
require File.join(content, "content", "clean_html")
|
10
10
|
require File.join(content, "content", "import_export_csv")
|
11
|
+
require File.join(content, "content", "despamilator")
|
11
12
|
|
12
13
|
module Gluttonberg
|
13
|
-
# The content module contains a whole bunch classes and mixins related to the
|
14
|
+
# The content module contains a whole bunch classes and mixins related to the
|
14
15
|
# pages, localizations, content models and helpers for rendering content
|
15
16
|
# inside of views.
|
16
17
|
module Content
|
@@ -19,7 +20,7 @@ module Gluttonberg
|
|
19
20
|
@@localizations = {}
|
20
21
|
@@localization_associations = nil
|
21
22
|
@@localization_classes = nil
|
22
|
-
|
23
|
+
|
23
24
|
# This is called after the application loads so that we can define any
|
24
25
|
# extra associations or do house-keeping once everything is required and
|
25
26
|
# running
|
@@ -29,10 +30,10 @@ module Gluttonberg
|
|
29
30
|
@@localization_classes = @@localizations.values
|
30
31
|
@@content_associations = Block.classes.collect { |k| k.association_name }
|
31
32
|
end
|
32
|
-
|
33
|
+
|
33
34
|
# For each content class that is registered, a corresponding association is
|
34
35
|
# declared against the Page model. We need to keep track of these, which
|
35
|
-
# is what this method does. It just returns an array of the association
|
36
|
+
# is what this method does. It just returns an array of the association
|
36
37
|
# names.
|
37
38
|
def self.non_localized_associations
|
38
39
|
@@non_localized_associations ||= begin
|
@@ -40,24 +41,24 @@ module Gluttonberg
|
|
40
41
|
non_localized.collect {|c| c.association_name }
|
41
42
|
end
|
42
43
|
end
|
43
|
-
|
44
|
+
|
44
45
|
# Return the collection of content association names.
|
45
46
|
def self.content_associations
|
46
47
|
@@content_associations
|
47
48
|
end
|
48
|
-
|
49
|
-
# If a content class has the is_localized declaration, this method is used
|
49
|
+
|
50
|
+
# If a content class has the is_localized declaration, this method is used
|
50
51
|
# to register it so we can keep track of all localized content.
|
51
52
|
def self.register_localization(assoc_name, klass)
|
52
53
|
@@localizations[assoc_name] = klass
|
53
54
|
end
|
54
|
-
|
55
|
-
# Returns a hash of content classes that are localized, keyed to the
|
55
|
+
|
56
|
+
# Returns a hash of content classes that are localized, keyed to the
|
56
57
|
# association name.
|
57
58
|
def self.localizations
|
58
59
|
@@localizations
|
59
60
|
end
|
60
|
-
|
61
|
+
|
61
62
|
# Returns an array of the localization association names.
|
62
63
|
def self.localization_associations
|
63
64
|
@@localization_associations
|