simple_forum 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +18 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +49 -0
- data/Rakefile +27 -0
- data/app/controllers/simple_forum/application_controller.rb +27 -0
- data/app/controllers/simple_forum/forums_controller.rb +29 -0
- data/app/controllers/simple_forum/posts_controller.rb +114 -0
- data/app/controllers/simple_forum/topics_controller.rb +116 -0
- data/app/models/simple_forum.rb +6 -0
- data/app/models/simple_forum/category.rb +28 -0
- data/app/models/simple_forum/forum.rb +69 -0
- data/app/models/simple_forum/moderatorship.rb +18 -0
- data/app/models/simple_forum/post.rb +105 -0
- data/app/models/simple_forum/topic.rb +134 -0
- data/app/models/simple_forum/user_activity.rb +80 -0
- data/app/views/layouts/simple_forum.html.erb +50 -0
- data/app/views/simple_forum/forums/index.html.erb +76 -0
- data/app/views/simple_forum/forums/show.html.erb +92 -0
- data/app/views/simple_forum/posts/edit.html.erb +41 -0
- data/app/views/simple_forum/topics/_post.html.erb +73 -0
- data/app/views/simple_forum/topics/new.html.erb +23 -0
- data/app/views/simple_forum/topics/show.html.erb +68 -0
- data/config/locales/simple_forum.pl.yml +121 -0
- data/config/routes.rb +44 -0
- data/lib/bb-ruby/bb-ruby.rb +57 -0
- data/lib/generators/simple_forum/install_generator.rb +19 -0
- data/lib/generators/templates/simple_forum.rb +34 -0
- data/lib/simple_forum.rb +52 -0
- data/lib/simple_forum/engine.rb +15 -0
- data/lib/simple_forum/tasks/railties.rake +66 -0
- data/lib/simple_forum/version.rb +4 -0
- metadata +114 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
module SimpleForum
|
2
|
+
class Forum < ::ActiveRecord::Base
|
3
|
+
|
4
|
+
set_table_name 'simple_forum_forums' #should work table_name_prefix in SimpleForum module but it's not!'
|
5
|
+
|
6
|
+
#acts_as_nested_set
|
7
|
+
|
8
|
+
has_many :topics,
|
9
|
+
:order => "#{SimpleForum::Topic.quoted_table_name}.last_updated_at DESC",
|
10
|
+
:dependent => :destroy,
|
11
|
+
:class_name => "SimpleForum::Topic"
|
12
|
+
|
13
|
+
belongs_to :recent_topic,
|
14
|
+
:class_name => 'SimpleForum::Topic'
|
15
|
+
|
16
|
+
has_many :posts,
|
17
|
+
:order => "#{SimpleForum::Post.quoted_table_name}.created_at DESC",
|
18
|
+
:class_name => 'SimpleForum::Post'
|
19
|
+
|
20
|
+
belongs_to :recent_post,
|
21
|
+
:class_name => 'SimpleForum::Post'
|
22
|
+
|
23
|
+
belongs_to :category,
|
24
|
+
:class_name => 'SimpleForum::Category'
|
25
|
+
|
26
|
+
has_many :moderatorships,
|
27
|
+
:class_name => 'SimpleForum::Moderatorship'
|
28
|
+
|
29
|
+
has_many :moderators,
|
30
|
+
:through => :moderatorships,
|
31
|
+
:source => :user
|
32
|
+
|
33
|
+
scope :default_order, order("#{quoted_table_name}.position ASC")
|
34
|
+
|
35
|
+
validates :name, :presence => true
|
36
|
+
validates :position, :presence => true, :numericality => {:only_integer => true, :allow_nil => true}
|
37
|
+
|
38
|
+
attr_accessible :name, :body, :parent_id, :position, :moderator_ids, :category_id
|
39
|
+
|
40
|
+
if respond_to?(:has_friendly_id)
|
41
|
+
has_friendly_id :name, :use_slug => true, :approximate_ascii => true
|
42
|
+
else
|
43
|
+
def to_param
|
44
|
+
"#{id}-#{name.to_s.parameterize}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def recent_activity?(user)
|
49
|
+
SimpleForum::UserActivity.new(user).recent_activity?(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def bang_recent_activity(user)
|
53
|
+
SimpleForum::UserActivity.new(user).bang(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def moderated_by?(user)
|
57
|
+
return false unless user
|
58
|
+
@moderated_by_cache ||= {}
|
59
|
+
if @moderated_by_cache.has_key?(user.id)
|
60
|
+
@moderated_by_cache[user.id]
|
61
|
+
else
|
62
|
+
@moderated_by_cache[user.id] = moderators.include?(user)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
alias_method :is_moderator?, :moderated_by?
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SimpleForum
|
2
|
+
class Moderatorship < ::ActiveRecord::Base
|
3
|
+
|
4
|
+
set_table_name 'simple_forum_moderatorships' #should work table_name_prefix in SimpleForum module but it's not!'
|
5
|
+
|
6
|
+
belongs_to :forum,
|
7
|
+
:class_name => "SimpleForum::Forum"
|
8
|
+
|
9
|
+
belongs_to :user,
|
10
|
+
:class_name => instance_eval(&AbstractAuth.invoke(:user_class)).name
|
11
|
+
|
12
|
+
validates :forum, :user, :presence => true
|
13
|
+
validates :user_id, :uniqueness => {:scope => :forum_id, :allow_nil => true}
|
14
|
+
|
15
|
+
attr_accessible :forum_id, :user_id
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module SimpleForum
|
2
|
+
class Post < ::ActiveRecord::Base
|
3
|
+
|
4
|
+
set_table_name 'simple_forum_posts' #should work table_name_prefix in SimpleForum module but it's not!'
|
5
|
+
|
6
|
+
belongs_to :user,
|
7
|
+
:class_name => instance_eval(&AbstractAuth.invoke(:user_class)).name
|
8
|
+
|
9
|
+
belongs_to :edited_by,
|
10
|
+
:class_name => instance_eval(&AbstractAuth.invoke(:user_class)).name
|
11
|
+
|
12
|
+
belongs_to :deleted_by,
|
13
|
+
:class_name => instance_eval(&AbstractAuth.invoke(:user_class)).name
|
14
|
+
|
15
|
+
belongs_to :topic,
|
16
|
+
:counter_cache => true,
|
17
|
+
:class_name => "SimpleForum::Topic"
|
18
|
+
|
19
|
+
belongs_to :forum,
|
20
|
+
:counter_cache => true,
|
21
|
+
:class_name => "SimpleForum::Forum"
|
22
|
+
|
23
|
+
before_validation :set_forum_id, :on => :create
|
24
|
+
|
25
|
+
after_create :update_cached_fields
|
26
|
+
after_destroy :update_cached_fields
|
27
|
+
|
28
|
+
scope :recent, order("#{quoted_table_name}.created_at DESC")
|
29
|
+
|
30
|
+
attr_accessible :body
|
31
|
+
validates :topic, :forum, :user, :presence => true
|
32
|
+
validates :body, :presence => true
|
33
|
+
|
34
|
+
validate :topic_must_not_be_closed, :on => :create
|
35
|
+
|
36
|
+
def topic_must_not_be_closed
|
37
|
+
errors.add(:base, I18n.t('simple_forum.errors.topic_is_close', :default => "Topic is closed.")) if topic && topic.is_closed?
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_page
|
41
|
+
before_count = topic.posts.where(["#{SimpleForum::Post.quoted_table_name}.created_at<?", created_at]).size
|
42
|
+
[((before_count + 1).to_f / SimpleForum::Post.per_page).ceil.to_i, 1].max
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.per_page
|
46
|
+
15
|
47
|
+
end
|
48
|
+
|
49
|
+
def output
|
50
|
+
body.to_s.bbcode_to_html
|
51
|
+
end
|
52
|
+
|
53
|
+
def output_without_tags
|
54
|
+
HTML::FullSanitizer.new.sanitize(output.gsub(/\<fieldset\>\<legend\>.*\<\/legend\>\<blockquote\>(.|\n)*\<\/blockquote\>/, ''))
|
55
|
+
end
|
56
|
+
|
57
|
+
def editable_by?(user, is_moderator=false)
|
58
|
+
return false if new_record?
|
59
|
+
return false if is_deleted?
|
60
|
+
is_moderator = forum.is_moderator?(user) if is_moderator.nil?
|
61
|
+
return true if is_moderator
|
62
|
+
created_at > SimpleForum.minutes_for_edit_post.minutes.ago && user == self.user
|
63
|
+
end
|
64
|
+
|
65
|
+
def deletable_by?(user, is_moderator=false)
|
66
|
+
return false if new_record?
|
67
|
+
return false if is_deleted?
|
68
|
+
is_moderator = forum.is_moderator?(user) if is_moderator.nil?
|
69
|
+
return true if is_moderator
|
70
|
+
created_at > SimpleForum.minutes_for_delete_post.minutes.ago && user == self.user
|
71
|
+
end
|
72
|
+
|
73
|
+
def is_deleted
|
74
|
+
!!deleted_at
|
75
|
+
end
|
76
|
+
|
77
|
+
alias_method :is_deleted?, :is_deleted
|
78
|
+
|
79
|
+
def is_edited
|
80
|
+
!!edited_at
|
81
|
+
end
|
82
|
+
|
83
|
+
alias_method :is_edited?, :is_edited
|
84
|
+
|
85
|
+
def mark_as_deleted_by(user)
|
86
|
+
return false unless deletable_by?(user, nil)
|
87
|
+
self.deleted_at = Time.now
|
88
|
+
self.deleted_by = user
|
89
|
+
self.save
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
def update_cached_fields
|
95
|
+
topic.update_cached_post_fields(self) if topic
|
96
|
+
if user && user.respond_to?(:forum_posts_count)
|
97
|
+
user.class.update_all({:forum_posts_count => SimpleForum::Post.where(:user_id => user.id).count}, {:id => user.id})
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_forum_id
|
102
|
+
self.forum = topic.forum if topic
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module SimpleForum
|
2
|
+
class Topic < ::ActiveRecord::Base
|
3
|
+
|
4
|
+
set_table_name 'simple_forum_topics' #should work table_name_prefix in SimpleForum module but it's not!'
|
5
|
+
|
6
|
+
belongs_to :user, :class_name => instance_eval(&AbstractAuth.invoke(:user_class)).name
|
7
|
+
|
8
|
+
belongs_to :forum,
|
9
|
+
:class_name => "SimpleForum::Forum", :counter_cache => true
|
10
|
+
|
11
|
+
has_many :posts,
|
12
|
+
:order => "#{SimpleForum::Post.quoted_table_name}.created_at ASC",
|
13
|
+
:class_name => "SimpleForum::Post",
|
14
|
+
:dependent => :delete_all
|
15
|
+
|
16
|
+
belongs_to :recent_post,
|
17
|
+
:class_name => "SimpleForum::Post"
|
18
|
+
|
19
|
+
has_one :last_post,
|
20
|
+
:order => "#{SimpleForum::Post.table_name}.created_at DESC",
|
21
|
+
:class_name => "SimpleForum::Post"
|
22
|
+
|
23
|
+
has_one :first_post,
|
24
|
+
:order => "#{SimpleForum::Post.quoted_table_name}.created_at ASC",
|
25
|
+
:class_name => "SimpleForum::Post"
|
26
|
+
|
27
|
+
|
28
|
+
validates :title, :forum, :presence => true
|
29
|
+
validates :user, :presence => true, :on => :create
|
30
|
+
validates :body, :presence => true, :on => :create
|
31
|
+
validate :forum_must_be_topicable, :on => :create
|
32
|
+
|
33
|
+
def forum_must_be_topicable
|
34
|
+
errors.add(:base, t('simple_forum.validations.forum_must_be_topicable')) if forum && !forum.is_topicable?
|
35
|
+
end
|
36
|
+
|
37
|
+
before_destroy :decrement_posts_counter_cache_for_forum
|
38
|
+
|
39
|
+
before_validation :set_default_attributes, :on => :create
|
40
|
+
after_create :create_initial_post
|
41
|
+
|
42
|
+
attr_accessor :body
|
43
|
+
attr_accessible :title, :body
|
44
|
+
|
45
|
+
def update_cached_post_fields(post)
|
46
|
+
if remaining_post = post.frozen? ? last_post : post
|
47
|
+
self.class.update_all({:last_updated_at => remaining_post.created_at,
|
48
|
+
:recent_post_id => remaining_post.id,
|
49
|
+
# :posts_count => posts.size
|
50
|
+
}, {:id => id})
|
51
|
+
forum.class.update_all({:recent_post_id => remaining_post.id}, {:id => forum.id})
|
52
|
+
else
|
53
|
+
destroy
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def paged?
|
58
|
+
posts.size > SimpleForum::Post.per_page
|
59
|
+
end
|
60
|
+
|
61
|
+
def last_page
|
62
|
+
@last_page ||= [(posts.size.to_f / SimpleForum::Post.per_page).ceil.to_i, 1].max
|
63
|
+
end
|
64
|
+
|
65
|
+
#return array with page numbers
|
66
|
+
# topic.page_numbers => [1, 2, 3, 4] #when pages count is 4
|
67
|
+
# topic.page_numbers => [1, 2, 3, 4, 5] #when pages count is 5
|
68
|
+
# topic.page_numbers => [1, nil, 3, 4, 5, 6] #when pages count is 6
|
69
|
+
# topic.page_numbers => [1, nil, 4, 5, 6, 7] #when pages count is 7
|
70
|
+
def page_numbers(max=5)
|
71
|
+
if last_page > max
|
72
|
+
[1] + [nil] + ((last_page-max+2)..last_page).to_a
|
73
|
+
else
|
74
|
+
(1..last_page).to_a
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
if respond_to?(:has_friendly_id)
|
79
|
+
has_friendly_id :title, :use_slug => true, :approximate_ascii => true
|
80
|
+
else
|
81
|
+
def to_param
|
82
|
+
"#{id}-#{title.to_s.parameterize}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def author?(u)
|
87
|
+
user == u
|
88
|
+
end
|
89
|
+
|
90
|
+
def is_open
|
91
|
+
!is_closed?
|
92
|
+
end
|
93
|
+
|
94
|
+
alias_method :is_open?, :is_open
|
95
|
+
|
96
|
+
def open!
|
97
|
+
update_attribute(:is_closed, false)
|
98
|
+
end
|
99
|
+
|
100
|
+
def close!
|
101
|
+
update_attribute(:is_closed, true)
|
102
|
+
end
|
103
|
+
|
104
|
+
def recent_activity?(user)
|
105
|
+
SimpleForum::UserActivity.new(user).recent_activity?(self)
|
106
|
+
end
|
107
|
+
|
108
|
+
def bang_recent_activity(user)
|
109
|
+
SimpleForum::UserActivity.new(user).bang(self)
|
110
|
+
end
|
111
|
+
|
112
|
+
def increment_views_count
|
113
|
+
self.class.increment_counter(:views_count, self)
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def decrement_posts_counter_cache_for_forum
|
119
|
+
forum.class.update_counters(forum.id, :posts_count => (-1) * posts.size) if forum
|
120
|
+
end
|
121
|
+
|
122
|
+
def set_default_attributes
|
123
|
+
self.last_updated_at ||= Time.now
|
124
|
+
end
|
125
|
+
|
126
|
+
def create_initial_post
|
127
|
+
p = self.posts.new(:body => @body) do |post|
|
128
|
+
post.user = user
|
129
|
+
end
|
130
|
+
p.save!
|
131
|
+
@body = nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'pstore'
|
2
|
+
|
3
|
+
class SimpleForum::UserActivity
|
4
|
+
|
5
|
+
PATH = Rails.root.join("file_store")
|
6
|
+
FILE = "simple_forum_activity"
|
7
|
+
FileUtils.mkdir_p(PATH)
|
8
|
+
|
9
|
+
attr_reader :user
|
10
|
+
|
11
|
+
def recent_activity?(object)
|
12
|
+
self[object] > Time.now
|
13
|
+
return false unless user.try(:id)
|
14
|
+
if object.is_a?(SimpleForum::Forum)
|
15
|
+
if recent_post = object.recent_post
|
16
|
+
recent_post.created_at > self[object]
|
17
|
+
else
|
18
|
+
false
|
19
|
+
end
|
20
|
+
else #SimpleForum::Topic
|
21
|
+
if object.last_updated_at
|
22
|
+
object.last_updated_at > self[object]
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(user)
|
30
|
+
@user = user
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.store
|
34
|
+
@@store ||= PStore.new(File.join(PATH, FILE))
|
35
|
+
end
|
36
|
+
|
37
|
+
def store
|
38
|
+
self.class.store
|
39
|
+
end
|
40
|
+
|
41
|
+
def [](object, default=Time.now)
|
42
|
+
ret = nil
|
43
|
+
store.transaction(true) do
|
44
|
+
user_hash = store[user.id] || {}
|
45
|
+
object_hash = user_hash[key_for_object(object)] || {}
|
46
|
+
ret = object_hash[object.id]
|
47
|
+
end if user.try(:id)
|
48
|
+
ret || default
|
49
|
+
end
|
50
|
+
|
51
|
+
def bang(object)
|
52
|
+
store.transaction do
|
53
|
+
user_hash = store[user.id] || {}
|
54
|
+
object_hash = (user_hash[key_for_object(object)] ||= {})
|
55
|
+
object_hash[object.id] = Time.now
|
56
|
+
store[user.id] = user_hash
|
57
|
+
end if user.try(:id)
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def destroy
|
62
|
+
store.transaction do
|
63
|
+
store.delete(user.id)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def key_for_object(object)
|
70
|
+
case object
|
71
|
+
when SimpleForum::Forum then
|
72
|
+
:f
|
73
|
+
when SimpleForum::Topic then
|
74
|
+
:t
|
75
|
+
else
|
76
|
+
object.model_name.cache_key
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="<%= I18n.locale %>">
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
5
|
+
<title><%= yield(:title) || '' %></title>
|
6
|
+
<%= favicon_link_tag %>
|
7
|
+
|
8
|
+
<%= stylesheet_link_tag('/simple_forum_engine/stylesheets/simple_forum') %>
|
9
|
+
|
10
|
+
<%= yield :stylesheets %>
|
11
|
+
|
12
|
+
<%= javascript_include_tag('https://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js') %>
|
13
|
+
<%= javascript_include_tag('rails') %>
|
14
|
+
|
15
|
+
<%= yield :javascripts %>
|
16
|
+
|
17
|
+
<%= yield :head %>
|
18
|
+
|
19
|
+
<%= csrf_meta_tag %>
|
20
|
+
</head>
|
21
|
+
<body>
|
22
|
+
|
23
|
+
<p class="logo">
|
24
|
+
<%= link_to image_tag('http://galdomedia.pl/images/logo.gif', :alt => 'Galdomedia'), "http://galdomedia.pl", :title => "Galdomedia" %>
|
25
|
+
</p>
|
26
|
+
|
27
|
+
<% if flash.present? %>
|
28
|
+
<div id="flash_messages">
|
29
|
+
<% flash.each do |key, val| %>
|
30
|
+
<%= content_tag :div, val, :class => key %>
|
31
|
+
<% end %>
|
32
|
+
</div>
|
33
|
+
<% end %>
|
34
|
+
|
35
|
+
<h1>Simple Forum</h1>
|
36
|
+
|
37
|
+
<div id="left">
|
38
|
+
<%= yield(:left_sidebar) %>
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<div id="right">
|
42
|
+
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<div id="content">
|
46
|
+
<%= yield %>
|
47
|
+
</div>
|
48
|
+
|
49
|
+
</body>
|
50
|
+
</html>
|