gluttonberg-core 2.5.5 → 2.5.6
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/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
|