thredded 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.mkdn +46 -0
- data/Rakefile +31 -0
- data/app/assets/javascripts/thredded/application.js +15 -0
- data/app/assets/stylesheets/thredded/application.css +13 -0
- data/app/controllers/thredded/application_controller.rb +50 -0
- data/app/controllers/thredded/messageboards_controller.rb +9 -0
- data/app/controllers/thredded/posts_controller.rb +86 -0
- data/app/controllers/thredded/preferences_controller.rb +20 -0
- data/app/controllers/thredded/private_topics_controller.rb +25 -0
- data/app/controllers/thredded/setups_controller.rb +39 -0
- data/app/controllers/thredded/topics_controller.rb +121 -0
- data/app/decorators/thredded/post_decorator.rb +52 -0
- data/app/decorators/thredded/topic_decorator.rb +96 -0
- data/app/helpers/thredded/application_helper.rb +9 -0
- data/app/helpers/thredded/messageboard_helper.rb +52 -0
- data/app/helpers/thredded/posts_helper.rb +7 -0
- data/app/helpers/thredded/topics_helper.rb +21 -0
- data/app/mailers/thredded/post_mailer.rb +28 -0
- data/app/mailers/thredded/topic_mailer.rb +29 -0
- data/app/models/thredded/ability.rb +50 -0
- data/app/models/thredded/attachment.rb +24 -0
- data/app/models/thredded/category.rb +10 -0
- data/app/models/thredded/image.rb +27 -0
- data/app/models/thredded/messageboard.rb +92 -0
- data/app/models/thredded/messageboard_decorator.rb +18 -0
- data/app/models/thredded/messageboard_preference.rb +15 -0
- data/app/models/thredded/null_preference.rb +9 -0
- data/app/models/thredded/null_topic_read.rb +13 -0
- data/app/models/thredded/null_user.rb +27 -0
- data/app/models/thredded/post.rb +75 -0
- data/app/models/thredded/post_notification.rb +6 -0
- data/app/models/thredded/private_topic.rb +33 -0
- data/app/models/thredded/private_user.rb +7 -0
- data/app/models/thredded/role.rb +18 -0
- data/app/models/thredded/topic.rb +145 -0
- data/app/models/thredded/topic_category.rb +7 -0
- data/app/models/thredded/user_detail.rb +6 -0
- data/app/models/thredded/user_extender.rb +27 -0
- data/app/models/thredded/user_preference.rb +6 -0
- data/app/models/thredded/user_topic_read.rb +67 -0
- data/app/uploaders/thredded/attachment_uploader.rb +30 -0
- data/app/uploaders/thredded/image_uploader.rb +17 -0
- data/app/views/thredded/messageboards/_messageboard.html.erb +17 -0
- data/app/views/thredded/messageboards/index.html.erb +15 -0
- data/app/views/thredded/post_mailer/at_notification.html.erb +17 -0
- data/app/views/thredded/post_mailer/at_notification.text.erb +13 -0
- data/app/views/thredded/posts/_form.html.erb +25 -0
- data/app/views/thredded/posts/_null_user.html.erb +0 -0
- data/app/views/thredded/posts/_post.html.erb +25 -0
- data/app/views/thredded/posts/_user.html.erb +7 -0
- data/app/views/thredded/posts/edit.html.erb +28 -0
- data/app/views/thredded/posts/index.html.erb +74 -0
- data/app/views/thredded/preferences/edit.html.erb +23 -0
- data/app/views/thredded/search/_form.html.erb +9 -0
- data/app/views/thredded/setups/_form.html.erb +35 -0
- data/app/views/thredded/setups/new.html.erb +5 -0
- data/app/views/thredded/shared/_currently_online.html.erb +10 -0
- data/app/views/thredded/shared/_error_msgs.html.erb +7 -0
- data/app/views/thredded/shared/_google_analytics.html.erb +10 -0
- data/app/views/thredded/shared/_topic_nav.html.erb +18 -0
- data/app/views/thredded/topic_mailer/message_notification.html.erb +17 -0
- data/app/views/thredded/topic_mailer/message_notification.text.erb +13 -0
- data/app/views/thredded/topics/_form.html.erb +6 -0
- data/app/views/thredded/topics/_topic_condensed.html.erb +20 -0
- data/app/views/thredded/topics/_topic_form.html.erb +27 -0
- data/app/views/thredded/topics/by_category.html.erb +51 -0
- data/app/views/thredded/topics/edit.html.erb +25 -0
- data/app/views/thredded/topics/index.html.erb +52 -0
- data/app/views/thredded/topics/menu/_new_topic.html.erb +3 -0
- data/app/views/thredded/topics/new.html.erb +18 -0
- data/app/views/thredded/topics/search.html.erb +45 -0
- data/config/routes.rb +26 -0
- data/db/migrate/20130425230852_create_thredded_tables.rb +143 -0
- data/lib/tasks/thredded_tasks.rake +4 -0
- data/lib/thredded.rb +38 -0
- data/lib/thredded/at_notification_extractor.rb +15 -0
- data/lib/thredded/at_notifier.rb +65 -0
- data/lib/thredded/at_users.rb +17 -0
- data/lib/thredded/email_processor.rb +74 -0
- data/lib/thredded/engine.rb +17 -0
- data/lib/thredded/filter.rb +4 -0
- data/lib/thredded/filter/at_notification.rb +11 -0
- data/lib/thredded/filter/attachment.rb +51 -0
- data/lib/thredded/filter/base.rb +13 -0
- data/lib/thredded/filter/bbcode.rb +75 -0
- data/lib/thredded/filter/emoji.rb +11 -0
- data/lib/thredded/filter/markdown.rb +25 -0
- data/lib/thredded/filter/syntax.rb +28 -0
- data/lib/thredded/filter/textile.rb +21 -0
- data/lib/thredded/messageboard_user_permissions.rb +27 -0
- data/lib/thredded/post_sql_builder.rb +34 -0
- data/lib/thredded/post_user_permissions.rb +37 -0
- data/lib/thredded/private_topic_notifier.rb +56 -0
- data/lib/thredded/private_topic_user_permissions.rb +32 -0
- data/lib/thredded/search_parser.rb +45 -0
- data/lib/thredded/search_sql_builder.rb +25 -0
- data/lib/thredded/setup_thredded.rb +7 -0
- data/lib/thredded/table_sql_builder.rb +105 -0
- data/lib/thredded/topic_sql_builder.rb +32 -0
- data/lib/thredded/topic_user_permissions.rb +51 -0
- data/lib/thredded/version.rb +3 -0
- metadata +537 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module Thredded
|
2
|
+
class MessageboardUserPermissions
|
3
|
+
attr_reader :messageboard, :user
|
4
|
+
|
5
|
+
def initialize(messageboard, user)
|
6
|
+
@messageboard = messageboard
|
7
|
+
@user = user
|
8
|
+
end
|
9
|
+
|
10
|
+
def readable?
|
11
|
+
(messageboard.restricted_to_private? && messageboard.has_member?(user)) ||
|
12
|
+
(messageboard.restricted_to_logged_in? && user.valid?) ||
|
13
|
+
messageboard.public?
|
14
|
+
end
|
15
|
+
|
16
|
+
def postable?
|
17
|
+
if messageboard.posting_for_anonymous? &&
|
18
|
+
(messageboard.restricted_to_private? || messageboard.restricted_to_logged_in?)
|
19
|
+
false
|
20
|
+
else
|
21
|
+
messageboard.posting_for_anonymous? ||
|
22
|
+
(messageboard.posting_for_logged_in? && user.try(:valid?)) ||
|
23
|
+
(messageboard.posting_for_members? && messageboard.has_member?(user))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Thredded
|
2
|
+
class PostSqlBuilder < TableSqlBuilder
|
3
|
+
def build_text_search
|
4
|
+
if text.present?
|
5
|
+
search_text = text
|
6
|
+
add_from('posts p')
|
7
|
+
add_where('t.id = p.topic_id')
|
8
|
+
add_where("to_tsvector('english', p.content) @@ plainto_tsquery('english', ?)", search_text.uniq.join(' '))
|
9
|
+
|
10
|
+
search_text.each do |term|
|
11
|
+
if (is_quoted(term))
|
12
|
+
add_where('p.content ILIKE ?', term.gsub('"', '%'))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_in_category
|
19
|
+
if categories.present?
|
20
|
+
add_from('topic_categories tc')
|
21
|
+
add_where('tc.topic_id = t.id')
|
22
|
+
add_where('tc.category_id in (?)', categories)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_by_user
|
27
|
+
if users.present?
|
28
|
+
add_from 'posts p'
|
29
|
+
add_where('t.id = p.topic_id')
|
30
|
+
add_where('p.user_id in (?)', users)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Thredded
|
2
|
+
class PostUserPermissions
|
3
|
+
attr_reader :post, :user, :user_details, :messageboard, :topic
|
4
|
+
|
5
|
+
def initialize(post, user, user_details)
|
6
|
+
@post = post
|
7
|
+
@topic = post.topic
|
8
|
+
@messageboard = post.messageboard
|
9
|
+
@user = user
|
10
|
+
@user_details = user_details || UserDetail.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def manageable?
|
14
|
+
created_post?
|
15
|
+
end
|
16
|
+
|
17
|
+
def creatable?
|
18
|
+
thread_is_not_locked? && can_create_topic?
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def created_post?
|
24
|
+
user.id == post.user_id
|
25
|
+
end
|
26
|
+
|
27
|
+
def thread_is_not_locked?
|
28
|
+
!@topic.locked?
|
29
|
+
end
|
30
|
+
|
31
|
+
def can_create_topic?
|
32
|
+
TopicUserPermissions.new(@topic, @user, @user_details).creatable?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Thredded
|
2
|
+
class PrivateTopicNotifier
|
3
|
+
def initialize(topic)
|
4
|
+
@post = topic.posts.first || Post.new
|
5
|
+
@topic = topic
|
6
|
+
end
|
7
|
+
|
8
|
+
def notifications_for_private_topic
|
9
|
+
members = private_topic_recipients
|
10
|
+
|
11
|
+
if members.present?
|
12
|
+
user_emails = members.map(&:email)
|
13
|
+
TopicMailer.message_notification(topic.id, user_emails).deliver
|
14
|
+
mark_notified(members)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def private_topic_recipients
|
19
|
+
members = topic.users - [topic.user]
|
20
|
+
members = exclude_those_opting_out_of_message_notifications(members)
|
21
|
+
members = exclude_previously_notified(members)
|
22
|
+
members
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :post, :topic
|
28
|
+
|
29
|
+
def mark_notified(members)
|
30
|
+
members.each do |member|
|
31
|
+
post.post_notifications.create(email: member.email)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def exclude_those_opting_out_of_message_notifications(members)
|
36
|
+
members.reject do |member|
|
37
|
+
!Thredded::MessageboardPreference
|
38
|
+
.for(member)
|
39
|
+
.in(topic.messageboard)
|
40
|
+
.first
|
41
|
+
.try(:notify_on_message?)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def notify_for_member_in_messageboard?(member, messageboard)
|
46
|
+
end
|
47
|
+
|
48
|
+
def exclude_previously_notified(members)
|
49
|
+
emails_notified = post.post_notifications.map(&:email)
|
50
|
+
|
51
|
+
members.reject do |member|
|
52
|
+
emails_notified.include? member.email
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Thredded
|
2
|
+
class PrivateTopicUserPermissions
|
3
|
+
attr_reader :private_topic, :user, :user_details, :messageboard
|
4
|
+
|
5
|
+
def initialize(private_topic, user, user_details)
|
6
|
+
@private_topic = private_topic
|
7
|
+
@messageboard = private_topic.messageboard
|
8
|
+
@user = user
|
9
|
+
@user_details = user_details || UserDetail.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def manageable?
|
13
|
+
user_started_topic?
|
14
|
+
end
|
15
|
+
|
16
|
+
def readable?
|
17
|
+
private_topic.users.include?(user)
|
18
|
+
end
|
19
|
+
|
20
|
+
def creatable?
|
21
|
+
TopicUserPermissions
|
22
|
+
.new(private_topic, user, user_details).creatable?
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def user_started_topic?
|
28
|
+
user.id == private_topic.user_id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Thredded
|
2
|
+
class SearchParser
|
3
|
+
def initialize(query)
|
4
|
+
@query = query
|
5
|
+
@keywords = ['in', 'by', 'order']
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse
|
9
|
+
parsed_input = parse_keywords
|
10
|
+
parsed_input['text'] = parse_text
|
11
|
+
parsed_input
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_keywords
|
15
|
+
found_terms_hash = {}
|
16
|
+
|
17
|
+
@keywords.each do |keyword|
|
18
|
+
regex = Regexp.new(keyword+'\s*:\s*\w+', Regexp::IGNORECASE)
|
19
|
+
keyword_scan = @query.scan(regex)
|
20
|
+
@query = @query.gsub(regex, '')
|
21
|
+
|
22
|
+
if keyword_scan.present?
|
23
|
+
keyword_scan.each do |term|
|
24
|
+
keyword_term = term.gsub(' ', '').split(':')
|
25
|
+
|
26
|
+
if found_terms_hash[keyword].nil?
|
27
|
+
found_terms_hash[keyword] = []
|
28
|
+
end
|
29
|
+
|
30
|
+
found_terms_hash[keyword] << keyword_term[1]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
found_terms_hash
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_text
|
39
|
+
regex = Regexp.new('\"[^"]*\"')
|
40
|
+
found_terms = @query.scan(regex)
|
41
|
+
@query = @query.sub(regex,'')
|
42
|
+
found_terms.concat(@query.split(/\s+/))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'thredded/topic_sql_builder'
|
2
|
+
require 'thredded/post_sql_builder'
|
3
|
+
|
4
|
+
module Thredded
|
5
|
+
class SearchSqlBuilder
|
6
|
+
def initialize(query, messageboard)
|
7
|
+
@topic_builder = TopicSqlBuilder.new(query, messageboard)
|
8
|
+
@post_builder = PostSqlBuilder.new(query, messageboard)
|
9
|
+
@order_by = 'ORDER BY updated_at DESC'
|
10
|
+
end
|
11
|
+
|
12
|
+
def build
|
13
|
+
[
|
14
|
+
'SELECT * FROM topics WHERE id IN (', @post_builder.sql,
|
15
|
+
'UNION', @topic_builder.sql,
|
16
|
+
')', @order_by,
|
17
|
+
'LIMIT 50'
|
18
|
+
].join(' ')
|
19
|
+
end
|
20
|
+
|
21
|
+
def binds
|
22
|
+
@post_builder.binds.concat(@topic_builder.binds)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'thredded/search_parser'
|
2
|
+
|
3
|
+
module Thredded
|
4
|
+
class TableSqlBuilder
|
5
|
+
attr_accessor :binds
|
6
|
+
|
7
|
+
def initialize(query, messageboard)
|
8
|
+
@terms = SearchParser.new(query).parse
|
9
|
+
|
10
|
+
@select = 'SELECT t.id'
|
11
|
+
@from = 'FROM topics t'
|
12
|
+
@where = ['t.messageboard_id = ?']
|
13
|
+
@binds = [messageboard.id]
|
14
|
+
|
15
|
+
@search_categories = []
|
16
|
+
@search_users = []
|
17
|
+
@search_text = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def sql
|
21
|
+
build_by_user
|
22
|
+
build_in_category
|
23
|
+
build_text_search
|
24
|
+
|
25
|
+
[@select,
|
26
|
+
@from,
|
27
|
+
'WHERE', @where.join(' AND '),
|
28
|
+
].join(' ')
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def is_quoted(term)
|
34
|
+
term.count('"') == 2
|
35
|
+
end
|
36
|
+
|
37
|
+
def categories
|
38
|
+
if @search_categories.any?
|
39
|
+
@search_categories
|
40
|
+
else
|
41
|
+
if @terms['in']
|
42
|
+
@terms['in'].each do |category_name|
|
43
|
+
category = Category
|
44
|
+
.where('lower(name) = ?', category_name.downcase).first
|
45
|
+
if category
|
46
|
+
@search_categories << category.id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@search_categories
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def users
|
56
|
+
if @search_users.any?
|
57
|
+
@search_users
|
58
|
+
else
|
59
|
+
|
60
|
+
if @terms['by']
|
61
|
+
@terms['by'].each do |username|
|
62
|
+
user = User.where('lower(name) = ?', username.downcase).first
|
63
|
+
|
64
|
+
if user
|
65
|
+
@search_users << user.id
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@search_users
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def text
|
75
|
+
@terms['text']
|
76
|
+
end
|
77
|
+
|
78
|
+
def build_by_user
|
79
|
+
raise 'SubclassResponsibility'
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_in_category
|
83
|
+
raise 'SubclassResponsibility'
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_text_search
|
87
|
+
raise 'SubclassResponsibility'
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_from(table)
|
91
|
+
if @from.exclude? table
|
92
|
+
@from = "#{@from}, #{table}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_where(where, binds=nil)
|
97
|
+
if @where.exclude? where
|
98
|
+
@where << where
|
99
|
+
if (binds.present?)
|
100
|
+
@binds.push(binds)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'thredded/table_sql_builder'
|
2
|
+
|
3
|
+
module Thredded
|
4
|
+
class TopicSqlBuilder < TableSqlBuilder
|
5
|
+
def build_text_search
|
6
|
+
if text.present?
|
7
|
+
search_text = text
|
8
|
+
add_where("to_tsvector('english', t.title) @@ plainto_tsquery('english', ?)", search_text.uniq.join(' '))
|
9
|
+
|
10
|
+
search_text.each do |term|
|
11
|
+
if (is_quoted(term))
|
12
|
+
add_where('t.title ILIKE ?', term.gsub('"', '%'))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_in_category
|
19
|
+
if categories.present?
|
20
|
+
add_from('topic_categories tc')
|
21
|
+
add_where('tc.topic_id = t.id')
|
22
|
+
add_where('tc.category_id in (?)', categories)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_by_user
|
27
|
+
if users.present?
|
28
|
+
add_where('t.user_id in (?)', users)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Thredded
|
2
|
+
class TopicUserPermissions
|
3
|
+
attr_reader :topic, :user, :user_details, :messageboard
|
4
|
+
|
5
|
+
def initialize(topic, user, user_details)
|
6
|
+
@topic = topic
|
7
|
+
@messageboard = topic.messageboard
|
8
|
+
@user = user
|
9
|
+
@user_details = user_details || UserDetail.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def creatable?
|
13
|
+
member? || messageboard_restrictions_allow?
|
14
|
+
end
|
15
|
+
|
16
|
+
def manageable?
|
17
|
+
superadmin? || started_by_user? || administrates_messageboard?
|
18
|
+
end
|
19
|
+
|
20
|
+
def readable?
|
21
|
+
MessageboardUserPermissions.new(messageboard, user).readable?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def messageboard_restrictions_allow?
|
27
|
+
user.valid? &&
|
28
|
+
(
|
29
|
+
messageboard.public? ||
|
30
|
+
messageboard.restricted_to_logged_in? ||
|
31
|
+
messageboard.posting_for_logged_in?
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def member?
|
36
|
+
user.valid? && messageboard.has_member?(user)
|
37
|
+
end
|
38
|
+
|
39
|
+
def superadmin?
|
40
|
+
user_details.superadmin?
|
41
|
+
end
|
42
|
+
|
43
|
+
def started_by_user?
|
44
|
+
topic.user_id == user.id
|
45
|
+
end
|
46
|
+
|
47
|
+
def administrates_messageboard?
|
48
|
+
user.valid? && messageboard.member_is_a?(user, 'admin')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|