thredded 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.mkdn +46 -0
  4. data/Rakefile +31 -0
  5. data/app/assets/javascripts/thredded/application.js +15 -0
  6. data/app/assets/stylesheets/thredded/application.css +13 -0
  7. data/app/controllers/thredded/application_controller.rb +50 -0
  8. data/app/controllers/thredded/messageboards_controller.rb +9 -0
  9. data/app/controllers/thredded/posts_controller.rb +86 -0
  10. data/app/controllers/thredded/preferences_controller.rb +20 -0
  11. data/app/controllers/thredded/private_topics_controller.rb +25 -0
  12. data/app/controllers/thredded/setups_controller.rb +39 -0
  13. data/app/controllers/thredded/topics_controller.rb +121 -0
  14. data/app/decorators/thredded/post_decorator.rb +52 -0
  15. data/app/decorators/thredded/topic_decorator.rb +96 -0
  16. data/app/helpers/thredded/application_helper.rb +9 -0
  17. data/app/helpers/thredded/messageboard_helper.rb +52 -0
  18. data/app/helpers/thredded/posts_helper.rb +7 -0
  19. data/app/helpers/thredded/topics_helper.rb +21 -0
  20. data/app/mailers/thredded/post_mailer.rb +28 -0
  21. data/app/mailers/thredded/topic_mailer.rb +29 -0
  22. data/app/models/thredded/ability.rb +50 -0
  23. data/app/models/thredded/attachment.rb +24 -0
  24. data/app/models/thredded/category.rb +10 -0
  25. data/app/models/thredded/image.rb +27 -0
  26. data/app/models/thredded/messageboard.rb +92 -0
  27. data/app/models/thredded/messageboard_decorator.rb +18 -0
  28. data/app/models/thredded/messageboard_preference.rb +15 -0
  29. data/app/models/thredded/null_preference.rb +9 -0
  30. data/app/models/thredded/null_topic_read.rb +13 -0
  31. data/app/models/thredded/null_user.rb +27 -0
  32. data/app/models/thredded/post.rb +75 -0
  33. data/app/models/thredded/post_notification.rb +6 -0
  34. data/app/models/thredded/private_topic.rb +33 -0
  35. data/app/models/thredded/private_user.rb +7 -0
  36. data/app/models/thredded/role.rb +18 -0
  37. data/app/models/thredded/topic.rb +145 -0
  38. data/app/models/thredded/topic_category.rb +7 -0
  39. data/app/models/thredded/user_detail.rb +6 -0
  40. data/app/models/thredded/user_extender.rb +27 -0
  41. data/app/models/thredded/user_preference.rb +6 -0
  42. data/app/models/thredded/user_topic_read.rb +67 -0
  43. data/app/uploaders/thredded/attachment_uploader.rb +30 -0
  44. data/app/uploaders/thredded/image_uploader.rb +17 -0
  45. data/app/views/thredded/messageboards/_messageboard.html.erb +17 -0
  46. data/app/views/thredded/messageboards/index.html.erb +15 -0
  47. data/app/views/thredded/post_mailer/at_notification.html.erb +17 -0
  48. data/app/views/thredded/post_mailer/at_notification.text.erb +13 -0
  49. data/app/views/thredded/posts/_form.html.erb +25 -0
  50. data/app/views/thredded/posts/_null_user.html.erb +0 -0
  51. data/app/views/thredded/posts/_post.html.erb +25 -0
  52. data/app/views/thredded/posts/_user.html.erb +7 -0
  53. data/app/views/thredded/posts/edit.html.erb +28 -0
  54. data/app/views/thredded/posts/index.html.erb +74 -0
  55. data/app/views/thredded/preferences/edit.html.erb +23 -0
  56. data/app/views/thredded/search/_form.html.erb +9 -0
  57. data/app/views/thredded/setups/_form.html.erb +35 -0
  58. data/app/views/thredded/setups/new.html.erb +5 -0
  59. data/app/views/thredded/shared/_currently_online.html.erb +10 -0
  60. data/app/views/thredded/shared/_error_msgs.html.erb +7 -0
  61. data/app/views/thredded/shared/_google_analytics.html.erb +10 -0
  62. data/app/views/thredded/shared/_topic_nav.html.erb +18 -0
  63. data/app/views/thredded/topic_mailer/message_notification.html.erb +17 -0
  64. data/app/views/thredded/topic_mailer/message_notification.text.erb +13 -0
  65. data/app/views/thredded/topics/_form.html.erb +6 -0
  66. data/app/views/thredded/topics/_topic_condensed.html.erb +20 -0
  67. data/app/views/thredded/topics/_topic_form.html.erb +27 -0
  68. data/app/views/thredded/topics/by_category.html.erb +51 -0
  69. data/app/views/thredded/topics/edit.html.erb +25 -0
  70. data/app/views/thredded/topics/index.html.erb +52 -0
  71. data/app/views/thredded/topics/menu/_new_topic.html.erb +3 -0
  72. data/app/views/thredded/topics/new.html.erb +18 -0
  73. data/app/views/thredded/topics/search.html.erb +45 -0
  74. data/config/routes.rb +26 -0
  75. data/db/migrate/20130425230852_create_thredded_tables.rb +143 -0
  76. data/lib/tasks/thredded_tasks.rake +4 -0
  77. data/lib/thredded.rb +38 -0
  78. data/lib/thredded/at_notification_extractor.rb +15 -0
  79. data/lib/thredded/at_notifier.rb +65 -0
  80. data/lib/thredded/at_users.rb +17 -0
  81. data/lib/thredded/email_processor.rb +74 -0
  82. data/lib/thredded/engine.rb +17 -0
  83. data/lib/thredded/filter.rb +4 -0
  84. data/lib/thredded/filter/at_notification.rb +11 -0
  85. data/lib/thredded/filter/attachment.rb +51 -0
  86. data/lib/thredded/filter/base.rb +13 -0
  87. data/lib/thredded/filter/bbcode.rb +75 -0
  88. data/lib/thredded/filter/emoji.rb +11 -0
  89. data/lib/thredded/filter/markdown.rb +25 -0
  90. data/lib/thredded/filter/syntax.rb +28 -0
  91. data/lib/thredded/filter/textile.rb +21 -0
  92. data/lib/thredded/messageboard_user_permissions.rb +27 -0
  93. data/lib/thredded/post_sql_builder.rb +34 -0
  94. data/lib/thredded/post_user_permissions.rb +37 -0
  95. data/lib/thredded/private_topic_notifier.rb +56 -0
  96. data/lib/thredded/private_topic_user_permissions.rb +32 -0
  97. data/lib/thredded/search_parser.rb +45 -0
  98. data/lib/thredded/search_sql_builder.rb +25 -0
  99. data/lib/thredded/setup_thredded.rb +7 -0
  100. data/lib/thredded/table_sql_builder.rb +105 -0
  101. data/lib/thredded/topic_sql_builder.rb +32 -0
  102. data/lib/thredded/topic_user_permissions.rb +51 -0
  103. data/lib/thredded/version.rb +3 -0
  104. 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,7 @@
1
+ module Thredded
2
+ class SetupThredded
3
+ def matches?(request)
4
+ return Thredded::Messageboard.all.empty?
5
+ end
6
+ end
7
+ 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