simple_forum 0.0.1
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/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>
|