wa_bcms_blog 1.1.0

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.
Files changed (51) hide show
  1. data/LICENSE.txt +165 -0
  2. data/README.markdown +101 -0
  3. data/app/controllers/application_controller.rb +10 -0
  4. data/app/controllers/cms/blog_comments_controller.rb +3 -0
  5. data/app/controllers/cms/blog_posts_controller.rb +35 -0
  6. data/app/controllers/cms/blogs_controller.rb +11 -0
  7. data/app/helpers/application_helper.rb +3 -0
  8. data/app/helpers/cms/blog_helper.rb +11 -0
  9. data/app/models/blog.rb +162 -0
  10. data/app/models/blog_comment.rb +32 -0
  11. data/app/models/blog_group_membership.rb +4 -0
  12. data/app/models/blog_post.rb +113 -0
  13. data/app/portlets/blog_post_portlet.rb +38 -0
  14. data/app/portlets/blog_posts_portlet.rb +50 -0
  15. data/app/views/cms/blog_comments/_form.html.erb +5 -0
  16. data/app/views/cms/blog_comments/render.html.erb +2 -0
  17. data/app/views/cms/blog_posts/_form.html.erb +11 -0
  18. data/app/views/cms/blog_posts/no_access.html.erb +9 -0
  19. data/app/views/cms/blog_posts/render.html.erb +1 -0
  20. data/app/views/cms/blogs/_form.html.erb +28 -0
  21. data/app/views/cms/blogs/admin_only.html.erb +9 -0
  22. data/app/views/cms/blogs/render.html.erb +2 -0
  23. data/app/views/partials/_blog_post.html.erb +103 -0
  24. data/app/views/partials/_blog_post.html.haml +91 -0
  25. data/app/views/portlets/blog_post/_form.html.erb +3 -0
  26. data/app/views/portlets/blog_post/render.html.erb +33 -0
  27. data/app/views/portlets/blog_posts/_form.html.erb +13 -0
  28. data/app/views/portlets/blog_posts/render.html.haml +9 -0
  29. data/db/migrate/20090415000000_create_blogs.rb +45 -0
  30. data/db/migrate/20090415000001_create_blog_posts.rb +25 -0
  31. data/db/migrate/20090415000002_create_blog_comments.rb +19 -0
  32. data/db/migrate/20090415000003_add_attachment_to_blog_posts.rb +23 -0
  33. data/db/migrate/20100521042244_add_moderate_comments_to_blog.rb +10 -0
  34. data/doc/README_FOR_APP +2 -0
  35. data/doc/migrate_to_20100427.rb +77 -0
  36. data/doc/release_notes.txt +40 -0
  37. data/lib/bcms_blog.rb +1 -0
  38. data/lib/bcms_blog/routes.rb +9 -0
  39. data/rails/init.rb +4 -0
  40. data/test/factories.rb +48 -0
  41. data/test/functional/blog_post_test.rb +42 -0
  42. data/test/functional/blog_test.rb +74 -0
  43. data/test/functional/cms/blog_posts_controller_test.rb +46 -0
  44. data/test/functional/cms/blogs_controller_test.rb +25 -0
  45. data/test/performance/browsing_test.rb +9 -0
  46. data/test/test_helper.rb +103 -0
  47. data/test/test_logging.rb +64 -0
  48. data/test/unit/blog_comment_test.rb +34 -0
  49. data/test/unit/blog_post_test.rb +43 -0
  50. data/test/unit/blog_test.rb +86 -0
  51. metadata +127 -0
@@ -0,0 +1,32 @@
1
+ class BlogComment < ActiveRecord::Base
2
+ acts_as_content_block :is_searachable => "body"
3
+ belongs_to :post, :class_name => "BlogPost", :counter_cache => "comments_count"
4
+
5
+ validates_presence_of :post_id, :author, :body
6
+
7
+ def before_create
8
+ self.published = true unless post.blog.moderate_comments?
9
+ end
10
+
11
+ def self.default_order
12
+ "blog_comments.created_at desc"
13
+ end
14
+
15
+ def self.default_order_for_search
16
+ default_order
17
+ end
18
+
19
+ def self.columns_for_index
20
+ [ {:label => "Comment", :method => :name, :order => "blog_comments.body" },
21
+ {:label => "Created At", :method => :formatted_created_at, :order => "blog_comments.created_at"} ]
22
+ end
23
+
24
+ def name
25
+ body ? body[0..50] : ""
26
+ end
27
+
28
+ def formatted_created_at
29
+ created_at.to_s(:date)
30
+ end
31
+
32
+ end
@@ -0,0 +1,4 @@
1
+ class BlogGroupMembership < ActiveRecord::Base
2
+ belongs_to :blog
3
+ belongs_to :group
4
+ end
@@ -0,0 +1,113 @@
1
+ class BlogPost < ActiveRecord::Base
2
+ acts_as_content_block :taggable => true
3
+
4
+ belongs_to_attachment
5
+ def set_attachment_file_path
6
+ # The default behavior is use /attachments/file.txt for the attachment path,
7
+ # assuming file.txt was the name of the file the user uploaded
8
+ # You should override this with your own strategy for setting the attachment path
9
+ super
10
+ end
11
+
12
+ def set_attachment_section
13
+ # The default behavior is to put all attachments in the root section
14
+ # Override this method if you would like to change that
15
+ super
16
+ end
17
+
18
+
19
+ before_save :set_published_at
20
+
21
+ belongs_to :blog
22
+ belongs_to_category
23
+ belongs_to :author, :class_name => "User"
24
+ has_many :comments, :class_name => "BlogComment", :foreign_key => "post_id"
25
+
26
+ before_validation :set_slug
27
+ validates_presence_of :name, :slug, :blog_id, :author_id
28
+
29
+ named_scope :published_between, lambda { |start, finish|
30
+ { :conditions => [
31
+ "blog_posts.published_at >= ? AND blog_posts.published_at < ?",
32
+ start, finish ] }
33
+ }
34
+
35
+ named_scope :not_tagged_with, lambda { |tag| {
36
+ :conditions => [
37
+ "blog_posts.id not in (
38
+ SELECT taggings.taggable_id FROM taggings
39
+ JOIN tags ON tags.id = taggings.tag_id
40
+ WHERE taggings.taggable_type = 'BlogPost'
41
+ AND (tags.name = ?)
42
+ )",
43
+ tag
44
+ ]
45
+ } }
46
+
47
+ INCORRECT_PARAMETERS = "Incorrect parameters. This is probably because you are trying to view the " +
48
+ "portlet through the CMS interface, and so we have no way of knowing what " +
49
+ "post(s) to show"
50
+
51
+ delegate :editable_by?, :to => :blog
52
+
53
+ def set_published_at
54
+ if !published_at && publish_on_save
55
+ self.published_at = Time.now
56
+ end
57
+ end
58
+
59
+ # This is necessary because, oddly, the publish! method in the Publishing behaviour sends an update
60
+ # query directly to the database, bypassing callbacks, so published_at does not get set by our
61
+ # set_published_at callback.
62
+ def after_publish_with_set_published_at
63
+ if published_at.nil?
64
+ self.published_at = Time.now
65
+ self.save!
66
+ end
67
+ after_publish_without_set_published_at if respond_to? :after_publish_without_set_published_at
68
+ end
69
+ if instance_methods.map(&:to_s).include? 'after_publish'
70
+ alias_method_chain :after_publish, :set_published_at
71
+ else
72
+ alias_method :after_publish, :after_publish_with_set_published_at
73
+ end
74
+
75
+ def self.default_order
76
+ "created_at desc"
77
+ end
78
+
79
+ def self.columns_for_index
80
+ [ {:label => "Name", :method => :name, :order => "name" },
81
+ {:label => "Published", :method => :published_label, :order => "published" } ]
82
+ end
83
+
84
+ def published_label
85
+ published_at ? published_at.to_s(:date) : nil
86
+ end
87
+
88
+ def set_slug
89
+ self.slug = name.to_slug
90
+ end
91
+
92
+ def path
93
+ send("#{blog.name_for_path}_post_path", route_params)
94
+ end
95
+ def route_name
96
+ "#{blog.name_for_path}_post"
97
+ end
98
+ def route_params
99
+ {:year => year, :month => month, :day => day, :slug => slug}
100
+ end
101
+
102
+ def year
103
+ published_at.strftime("%Y") unless published_at.blank?
104
+ end
105
+
106
+ def month
107
+ published_at.strftime("%m") unless published_at.blank?
108
+ end
109
+
110
+ def day
111
+ published_at.strftime("%d") unless published_at.blank?
112
+ end
113
+ end
@@ -0,0 +1,38 @@
1
+ class BlogPostPortlet < Portlet
2
+ #render_inline false
3
+ #enable_template_editor false
4
+
5
+ def render
6
+ scope = Blog.find(self.blog_id).posts
7
+ if params[:blog_post_id]
8
+ @blog_post = scope.find(params[:blog_post_id])
9
+ elsif params[:slug]
10
+ if params[:year]
11
+ date = Date.new(params[:year].to_i, params[:month].to_i, params[:day].to_i)
12
+ scope = scope.published_between(date, date + 1.day)
13
+ end
14
+ @blog_post = scope.find_by_slug!(params[:slug])
15
+ else
16
+ raise BlogPost::INCORRECT_PARAMETERS
17
+ end
18
+
19
+ pmap = flash[instance_name] || params
20
+ pmap[:blog_comment] ||= {}
21
+
22
+ @blog_comment = @blog_post.comments.build pmap[:blog_comment]
23
+ @blog_comment.errors.add_from_hash flash["#{instance_name}_errors"]
24
+ end
25
+
26
+ def create_comment
27
+ params[:blog_comment].merge! :ip => request.remote_ip
28
+ blog_comment = BlogComment.new(params[:blog_comment])
29
+ if blog_comment.valid? && blog_comment.save
30
+ url_for_success
31
+ else
32
+ store_params_in_flash
33
+ store_errors_in_flash(blog_comment.errors)
34
+ url_for_failure
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,50 @@
1
+ class BlogPostsPortlet < Portlet
2
+ def after_initialize
3
+ self.render_blog_post_code ||= 'truncate(blog_post.name, 30)'
4
+ end
5
+
6
+ # Mark this as 'true' to allow the portlet's template to be editable via the CMS admin UI.
7
+ enable_template_editor false
8
+
9
+ def render(_options = {})
10
+ # Since we can't pass any options to render_portlet, you can use $blog_posts_portlet_options if you want to pass in/override some options
11
+ # This is an ugly workaround for https://browsermedia.lighthouseapp.com/projects/28481-browsercms-30/tickets/350-make-it-possible-to-pass-options-to-render_portletrender_connectable in case we want to call render_portlet with some options
12
+ _options = $blog_posts_portlet_options || {}
13
+ _options.symbolize_keys!
14
+
15
+ portlet_attributes = self.portlet_attributes.inject({}) {|hash, a| hash[a.name] = a.value; hash}.reject {|k,v| v.blank?}.symbolize_keys
16
+
17
+ #options.reverse_merge!(params.slice(:tags)).symbolize_keys!
18
+ @options = portlet_attributes.merge(_options)
19
+ Rails.logger.debug "... BlogPostsPortlet#render(options=#{@options.inspect} #{@options.class})"
20
+
21
+ if @options[:blog_id]
22
+ finder = Blog.find(@options[:blog_id]).posts
23
+ elsif @options[:blog_name]
24
+ finder = Blog.find_by_name(@options[:blog_name]).posts
25
+ else
26
+ finder = BlogPost
27
+ end
28
+
29
+ if @options[:tags].is_a?(Array) && @options[:tags].size > 1
30
+ other_tags = @options[:tags][1..-1]
31
+ @options[:tags] = @options[:tags][0]
32
+ end
33
+
34
+ finder = finder.published
35
+ finder = Blog.posts_finder(finder, @options)
36
+
37
+ @blog_posts = finder.all(
38
+ :limit => @options[:limit] || 25,
39
+ :order => "published_at desc"
40
+ )
41
+
42
+ if other_tags
43
+ @blog_posts.select! {|p| (p.tags.map(&:name).map(&:downcase) & other_tags.map(&:downcase)).size == other_tags.size }
44
+ end
45
+
46
+ raise ActiveRecord::RecordNotFound.new("No articles found") if @blog_posts.empty?
47
+
48
+ @portlet = self
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ <%= f.cms_drop_down :post_id, BlogPost.all(:order => "name").map{|p| [p.name, p.id]} %>
2
+ <%= f.cms_text_field :author %>
3
+ <%= f.cms_text_field :email %>
4
+ <%= f.cms_text_field :url %>
5
+ <%= f.cms_text_area :body %>
@@ -0,0 +1,2 @@
1
+ <%= link_to h(@content_block.post.name), cms_blog_post_path(@content_block.post) %>
2
+ <p><%=h @content_block.body %></p>
@@ -0,0 +1,11 @@
1
+ <%= f.cms_text_field :name, :label => 'Title' %>
2
+ <%= f.cms_text_area :summary, :style => "height: 200px", :instructions => 'This will be displayed on the list page with a Read More link if present' %>
3
+ <%= f.cms_text_editor :body %>
4
+ <%= f.cms_datetime_select :published_at, :label => "Date", :instructions=>"The 'published' date/time for the post which is displayed publically." %>
5
+ <% unless @block.new_record? %>
6
+ <%= f.cms_drop_down :author_id, @block.blog.potential_authors.map { |u| ["#{u.full_name} (#{u.login})", u.id] } %>
7
+ <% end %>
8
+ <%= f.cms_drop_down :category_id, categories_for('Blog Post').map{|c| [c.path, c.id]}, :include_blank => true %>
9
+ <%= f.cms_tag_list %>
10
+ <%= f.cms_drop_down :blog_id, Blog.editable_by(current_user).map{|b| [b.name, b.id]} %>
11
+ <%= f.cms_file_field :attachment_file, :label => "Image" %>
@@ -0,0 +1,9 @@
1
+ <% content_for(:html_head) do %>
2
+ <%= stylesheet_link_tag "cms/content_library" %>
3
+ <% end %>
4
+ <% page_title "Content Library / List #{content_type.display_name_plural}" %>
5
+ <% @toolbar_title = "List #{content_type.display_name_plural}" %>
6
+
7
+ <div class="roundedcorners">
8
+ <p>Sorry, you don't have the permissions to write blog posts.</p>
9
+ </div>
@@ -0,0 +1 @@
1
+ <%= render :partial => "partials/blog_post", :object => @content_block %>
@@ -0,0 +1,28 @@
1
+ <%= f.cms_text_field :name %>
2
+ <%= f.cms_check_box :moderate_comments %>
3
+
4
+ <div class="checkbox_group fields" style="float: left; width: 100%">
5
+ <label>Permissions</label>
6
+ <%= hidden_field_tag "blog[group_ids][]", "", :id => nil %>
7
+ <div class="checkboxes">
8
+ <% for group in Group.cms_access.all(:order => "groups.name") %>
9
+ <div class="checkbox_fields">
10
+ <%= check_box_tag "blog[group_ids][]", group.id,
11
+ @block.groups.include?(group), :class => "cms_group_ids", :id => "cms_group_ids_#{group.id}", :tabindex => next_tabindex %>
12
+ <label for="cms_group_ids_<%= group.id %>"><%= group.name %></label>
13
+ </div>
14
+ <% end %>
15
+ <div class="instructions">Which &ldquo;CMS&rdquo; groups can edit and publish the blog?</div>
16
+ <div class="check_uncheck">
17
+ <%= link_to_check_all 'input.cms_group_ids' %>,
18
+ <%= link_to_uncheck_all 'input.cms_group_ids' %>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ <br clear="all" />
23
+
24
+ <%= f.cms_text_area :template, :default_value => Blog.default_template %>
25
+
26
+ <div class="instructions" style="float:none">
27
+ Saving your blog might take a couple of seconds while everything is set up.
28
+ </div>
@@ -0,0 +1,9 @@
1
+ <% content_for(:html_head) do %>
2
+ <%= stylesheet_link_tag "cms/content_library" %>
3
+ <% end %>
4
+ <% page_title "Content Library / List #{content_type.display_name_plural}" %>
5
+ <% @toolbar_title = "List #{content_type.display_name_plural}" %>
6
+
7
+ <div class="roundedcorners">
8
+ <p>Sorry, this section is restricted to administrators.</p>
9
+ </div>
@@ -0,0 +1,2 @@
1
+ <% page_title @page_title || @blog.name %>
2
+ <%= render :partial => "partials/blog_post", :collection => @blog_posts %>
@@ -0,0 +1,103 @@
1
+ <style>
2
+ .blog_post {
3
+ border-bottom: 1px solid #ccc;
4
+ margin-bottom: 20px;
5
+ }
6
+
7
+ .blog_post h2 {
8
+ margin-bottom: 0
9
+ }
10
+
11
+ .blog_post h2,
12
+ .blog_post h2 a {
13
+ font: normal 30px/28px 'arial black', arial, sans-serif !important;
14
+ text-decoration: none !important;
15
+ }
16
+ .blog_post h2 a:hover {
17
+ text-decoration: underline !important;
18
+ }
19
+
20
+ .blog_post .image {
21
+ float: left;
22
+ border: 1px solid #ccc;
23
+ margin: 10px;
24
+ margin-top: 0;
25
+ }
26
+ .blog_post .date {
27
+ color: #666;
28
+ }
29
+ .blog_post .read_more {
30
+ font-weight: bold;
31
+ }
32
+
33
+ .blog_post .comment + .comment {
34
+ border-top: 1px dashed #ccc;
35
+ }
36
+
37
+ .clear {
38
+ clear: both;
39
+ }
40
+ </style>
41
+
42
+ <%
43
+ # _counter is defined only if we pass :collection to the partial
44
+ if defined?(blog_post_counter)
45
+ showing_individual_post = false
46
+ else
47
+ showing_individual_post = true
48
+ blog_post_counter = 0
49
+ end
50
+ %>
51
+
52
+ <div id="blog_post_<%= blog_post.id %>" class="blog_post clear">
53
+ <% if blog_post.attachment %>
54
+ <div class="image">
55
+ <%# image_tag_with_max_size blog_post.attachment.file_path, blog_post.attachment.full_file_location, :width => max_width %>
56
+ <%= image_tag blog_post.attachment.file_path %>
57
+ </div>
58
+ <% end %>
59
+
60
+ <h2><%= link_to h(blog_post.name), href = _blog_post_path(blog_post) %></h2>
61
+
62
+ <div class="date"><%= blog_post.published_at.to_s(:long) %></div>
63
+
64
+ <div class="body">
65
+ <% if showing_individual_post or blog_post.summary.blank? %>
66
+ <%= blog_post.body %>
67
+ <% else %>
68
+ <%= blog_post.summary %>
69
+ <p class="read_more">
70
+ <%= link_to 'Read More »', href %>
71
+ </p>
72
+ <% end %>
73
+ </div>
74
+
75
+ <div class="meta">
76
+ <% unless blog_post.category_id.blank? %>
77
+ Posted in <%= link_to h(blog_post.category_name), _blog_path(blog_post.blog, 'posts_in_category', :category => blog_post.category_name) %>
78
+ <strong>|</strong>
79
+ <% end %>
80
+
81
+ <% if blog_post.tags.any? %>
82
+ Tags:
83
+ <span class="tags">
84
+ <%= blog_post.tags.map{|t| link_to(h(t.name), _blog_path(blog_post.blog, 'posts_with_tag', :tag => t.name)) }.join(", ") %>
85
+ </span>
86
+ <strong>|</strong>
87
+ <% end %>
88
+
89
+ <%= link_to h(pluralize(blog_post.comments.published.count, "Comment")), "#{_blog_post_path(blog_post)}#comments" %>
90
+ </div>
91
+ <br class="clear" />
92
+
93
+ <% comments = blog_post.comments.published.reject(&:new_record?) %>
94
+ <% if showing_individual_post and comments.any? -%>
95
+ <h2>Comments</h2>
96
+ <% comments.each_with_index do |comment, i| %>
97
+ <div class="comment <%= 'first' if i == 0 %>">
98
+ <%= h comment.body %>
99
+ <p>&mdash;<%= comment.url.present? ? link_to(h(comment.author), comment.url) : h(comment.author) %></p>
100
+ </div>
101
+ <% end %>
102
+ <% end %>
103
+ </div>
@@ -0,0 +1,91 @@
1
+ %style
2
+ :sass
3
+ .blog_post.first
4
+ h2, h2 a
5
+ font: normal 30px/28px 'arial black', arial, sans-serif !important
6
+ border-bottom: 1px solid #ccc
7
+
8
+ .blog_post
9
+ border: 0px solid gray
10
+ margin-bottom: 20px
11
+
12
+ h2
13
+ margin-bottom: 0
14
+ h2, h2 a
15
+ font: normal 20px/23px 'arial black', arial, sans-serif !important
16
+ text-decoration: none !important
17
+ h2 a:hover
18
+ text-decoration: underline !important
19
+
20
+ .image
21
+ float: left
22
+ border: 1px solid #ccc
23
+ margin: 10px
24
+ margin-top: 0
25
+
26
+ .date
27
+ color: #666
28
+ .read_more
29
+ font-weight: bold
30
+
31
+ .comment + .comment
32
+ border-top: 1px dashed #ccc
33
+
34
+ .clear
35
+ clear: both
36
+
37
+ :ruby
38
+ # _counter is defined only if we pass :collection to the partial
39
+ if defined?(blog_post_counter)
40
+ showing_individual_post = false
41
+ else
42
+ showing_individual_post = true
43
+ blog_post_counter = 0
44
+ end
45
+
46
+ if blog_post_counter == 0
47
+ max_width = 250
48
+ css_class = 'first'
49
+ else
50
+ max_width = 75
51
+ css_class = ''
52
+ end
53
+
54
+ .blog_post.clear{:id => "blog_post_#{blog_post.id}", :class => css_class}
55
+ - if blog_post.attachment
56
+ -# .image= image_tag_with_max_size blog_post.attachment.file_path, blog_post.attachment.full_file_location, :width => max_width
57
+ .image= image_tag blog_post.attachment.file_path
58
+
59
+ %h2= link_to h(blog_post.name), href = _blog_post_path(blog_post)
60
+
61
+ .date= blog_post.published_at.to_s(:long)
62
+
63
+ .body
64
+ - if showing_individual_post or blog_post.summary.blank?
65
+ = blog_post.body
66
+ - else
67
+ = blog_post.summary
68
+ %p.read_more= link_to 'Read More »', href
69
+
70
+ - if showing_individual_post || blog_post_counter == 0
71
+ .meta
72
+ - unless blog_post.category_id.blank?
73
+ Posted in #{link_to h(blog_post.category_name), _blog_path(blog_post.blog, 'posts_in_category', :category => blog_post.category_name)}
74
+ %strong |
75
+ - if blog_post.tags.any?
76
+ Tags:
77
+ %span.tags
78
+ = blog_post.tags.map{|t| link_to(h(t.name), _blog_path(blog_post.blog, 'posts_with_tag', :tag => t.name)) }.join(", ")
79
+ %strong |
80
+ = link_to h(pluralize(blog_post.comments_count, "Comment")), "#{_blog_post_path(blog_post)}#comments"
81
+ %br.clear/
82
+
83
+ - comments = blog_post.comments.reject(&:new_record?)
84
+ - if showing_individual_post and comments.any?
85
+ %h2 Comments
86
+ - comments.each_with_index do |comment, i|
87
+ %div{:class => "comment #{'first' if i == 0}"}
88
+ = h comment.body
89
+ %p
90
+ \&mdash;#{comment.url.present? ? link_to(h(comment.author), comment.url) : h(comment.author)}
91
+