<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>db/migrate/058_add_anonymous_commenting_fields.rb</filename>
    </added>
    <added>
      <filename>engine_config/initializers/recaptcha_constants.rb</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/.gitignore</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/LICENSE</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/README.rdoc</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/Rakefile</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/init.rb</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/install.rb</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/lib/recaptcha.rb</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/tasks/recaptcha_tasks.rake</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/test/recaptcha_test.rb</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/test/verify_recaptcha_test.rb</filename>
    </added>
    <added>
      <filename>engine_plugins/recaptcha/uninstall.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,9 @@
 class CommentsController &lt; BaseController
   before_filter :login_required, :except =&gt; [:index]
+  if AppConfig.allow_anonymous_commenting
+    skip_before_filter :login_required, :only =&gt; [:create]
+  end
+  
   uses_tiny_mce(:options =&gt; AppConfig.simple_mce_options, :only =&gt; [:index])
 
   cache_sweeper :comment_sweeper, :only =&gt; [:create, :destroy]
@@ -81,13 +85,15 @@ class CommentsController &lt; BaseController
     @commentable = Inflector.constantize(Inflector.camelize(params[:commentable_type])).find(params[:commentable_id])
     @comment = Comment.new(params[:comment])
     @comment.recipient = @commentable.owner
-    @comment.user_id = current_user.id
+    
+    @comment.user_id = current_user.id if current_user
+    @comment.author_ip = request.remote_ip #save the ip address for everyone, just because
     
     respond_to do |format|
-      if @comment.save
+      if verify_recaptcha(@comment) &amp;&amp; @comment.save
         @commentable.add_comment @comment
-        UserNotifier.deliver_comment_notice(@comment) if should_receive_notification(@comment)
-        deliver_comment_notice_to_previous_commenters(@comment)        
+        UserNotifier.deliver_comment_notice(@comment) if @comment.should_notify_recipient?
+        @comment.notify_previous_commenters
                 
         flash.now[:notice] = 'Comment was successfully created.'.l        
         format.html { redirect_to :controller =&gt; Inflector.underscore(params[:commentable_type]).pluralize, :action =&gt; 'show', :id =&gt; params[:commentable_id], :user_id =&gt; @comment.recipient.id }
@@ -95,7 +101,7 @@ class CommentsController &lt; BaseController
           render :partial =&gt; 'comments/comment.html.haml', :locals =&gt; {:comment =&gt; @comment, :highlighted =&gt; true}
         }
       else
-        flash.now[:error] = :comment_save_error.l_with_args(:error =&gt; @comment.errors.full_messages.join(&quot;, &quot;))
+        flash.now[:error] = :comment_save_error.l_with_args(:error =&gt; @comment.errors.full_messages.to_sentence)
         format.html { redirect_to :controller =&gt; Inflector.underscore(params[:commentable_type]).pluralize, :action =&gt; 'show', :id =&gt; params[:commentable_id] }
         format.js{
           render :inline =&gt; flash[:error], :status =&gt; 500
@@ -121,18 +127,5 @@ class CommentsController &lt; BaseController
     end    
   end
   
-  protected
-  
-  def deliver_comment_notice_to_previous_commenters(comment)
-    comment.previous_commenters_to_notify.each do |user|
-        UserNotifier.deliver_follow_up_comment_notice(user, comment)
-    end
-  end  
-  
-  def should_receive_notification(comment)
-    return false if comment.recipient.eql?(@comment.user)
-    return false unless comment.recipient.notify_comments?
-    true
-  end
 
 end
\ No newline at end of file</diff>
      <filename>app/controllers/comments_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,7 @@
 require 'pp'
 
 class PhotosController &lt; BaseController
-  before_filter :login_required, :only =&gt; [:new, :edit, :update, :destroy, :create, :manage_photos, :swfupload]
+  before_filter :login_required, :only =&gt; [:new, :edit, :update, :destroy, :create, :swfupload]
   before_filter :find_user, :only =&gt; [:new, :edit, :index, :show, :slideshow, :swfupload]
   before_filter :require_current_user, :only =&gt; [:new, :edit, :update, :destroy, :swfupload]
 
@@ -49,15 +49,18 @@ class PhotosController &lt; BaseController
   end
   
   def manage_photos
-    @user = current_user
-    cond = Caboose::EZ::Condition.new
-    cond.user_id == @user.id
-    if params[:tag_name]    
-      cond.append ['tags.name = ?', params[:tag_name]]
+    if logged_in?
+      @user = current_user
+      cond = Caboose::EZ::Condition.new
+      cond.user_id == @user.id
+      if params[:tag_name]    
+        cond.append ['tags.name = ?', params[:tag_name]]
+      end
+  
+      @selected = params[:photo_id]
+      @pages, @photos = paginate :photos, :order =&gt; &quot;created_at DESC&quot;, :conditions =&gt; cond.to_sql, :include =&gt; :tags, :per_page =&gt; 10
+
     end
-    
-    @selected = params[:photo_id]
-    @pages, @photos = paginate :photos, :order =&gt; &quot;created_at DESC&quot;, :conditions =&gt; cond.to_sql, :include =&gt; :tags, :per_page =&gt; 10
     respond_to do |format|
       format.js
     end</diff>
      <filename>app/controllers/photos_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,5 @@
 class PostsController &lt; BaseController
+
   include Viewable
   uses_tiny_mce(:options =&gt; AppConfig.default_mce_options, :only =&gt; [:new, :edit, :update, :create ])
   uses_tiny_mce(:options =&gt; AppConfig.simple_mce_options, :only =&gt; [:show])</diff>
      <filename>app/controllers/posts_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -6,7 +6,6 @@ class Comment &lt; ActiveRecord::Base
   belongs_to :user
   belongs_to :recipient, :class_name =&gt; &quot;User&quot;, :foreign_key =&gt; &quot;recipient_id&quot;
   
-  validates_presence_of :user
   validates_presence_of :comment
   validates_presence_of :recipient
   
@@ -14,7 +13,12 @@ class Comment &lt; ActiveRecord::Base
   
   before_save :whitelist_attributes  
 
-  acts_as_activity :user  
+  validates_presence_of :user, :unless =&gt; Proc.new{|record| AppConfig.allow_anonymous_commenting }
+  validates_presence_of :author_email, :unless =&gt; Proc.new{|record| record.user }  #require email unless logged in
+  validates_presence_of :author_ip, :unless =&gt; Proc.new{|record| record.user} #log ip unless logged in
+  validates_format_of :author_url, :with =&gt; /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix, :unless =&gt; Proc.new{|record| record.user }
+
+  acts_as_activity :user, :if =&gt; Proc.new{|record| record.user } #don't record an activity if there's no user
 
   def self.find_photo_comments_for(user)
     Comment.find(:all, :conditions =&gt; [&quot;recipient_id = ? AND commentable_type = ?&quot;, user.id, 'Photo'], :order =&gt; 'created_at DESC')
@@ -67,7 +71,11 @@ class Comment &lt; ActiveRecord::Base
   end
   
   def title_for_rss
-    &quot;Comment from #{user.login}&quot;
+    &quot;Comment from #{username}&quot;
+  end
+  
+  def username
+    user ? user.login : (author_name.blank? ? 'Anonymous' : author_name)
   end
   
   def self.find_recent(options = {:limit =&gt; 5})
@@ -78,6 +86,17 @@ class Comment &lt; ActiveRecord::Base
     person &amp;&amp; (person.admin? || person.eql?(user) || person.eql?(recipient) )
   end
   
+  def should_notify_recipient?
+    return false if recipient.eql?(user)
+    return false unless recipient.notify_comments?
+    true    
+  end
+  
+  def notify_previous_commenters
+    previous_commenters_to_notify.each do |commenter|
+      UserNotifier.deliver_follow_up_comment_notice(commenter, self)
+    end    
+  end
   
   protected
   def whitelist_attributes</diff>
      <filename>app/models/comment.rb</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@ class UserNotifier &lt; ActionMailer::Base
   def comment_notice(comment)
     @recipients  = &quot;#{comment.recipient.email}&quot;
     setup_sender_info
-    @subject     = &quot;#{comment.user.login} has something to say to you on #{AppConfig.community_name}!&quot;
+    @subject     = &quot;#{comment.username} has something to say to you on #{AppConfig.community_name}!&quot;
     @sent_on     = Time.now
     @body[:url]  = comment.generate_commentable_url
     @body[:user] = comment.recipient
@@ -39,7 +39,7 @@ class UserNotifier &lt; ActionMailer::Base
   def follow_up_comment_notice(user, comment)
     @recipients  = &quot;#{user.email}&quot;
     setup_sender_info
-    @subject     = &quot;#{comment.user.login} has commented on a #{comment.commentable_type} that you also commented on. [#{AppConfig.community_name}]&quot;
+    @subject     = &quot;#{comment.username} has commented on a #{comment.commentable_type} that you also commented on. [#{AppConfig.community_name}]&quot;
     @sent_on     = Time.now
     @body[:url]  = comment.generate_commentable_url
     @body[:user] = user</diff>
      <filename>app/models/user_notifier.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,25 +1,39 @@
-.hentry{:id =&gt; &quot;comment_#{comment.id}&quot;}
-  .vcard.author
-    %h5= link_to image_tag(comment.user.avatar_photo_url(:thumb), :height =&gt; '50', :width =&gt; '50', :alt =&gt; &quot;#{comment.user.login}&quot;), user_path(comment.user), :rel =&gt; 'bookmark', :class =&gt; 'photo', :title =&gt; &quot;#{comment.user.login}'s profile&quot;
-    %ul
-      %li.fn
-        = link_to comment.user.login, user_path(comment.user), :class =&gt; 'url'
-      %li.update
-        %a{&quot;href&quot;=&gt;&quot;#{comment.generate_commentable_url}&quot;, &quot;rel&quot;=&gt;&quot;bookmark&quot;}
-          %abbr.published{&quot;title&quot;=&gt;&quot;#{comment.created_at}&quot;}
-            = comment.created_at.strftime(&quot;%B %d, %Y&quot;)
-      %li.addfriend
-        %a{&quot;href&quot;=&gt;&quot;#&quot;}
-          =&quot;Add as friend&quot;.l
-      -if ( comment.can_be_deleted_by(current_user) )
-        %li.delete=link_to_remote(&quot;Delete&quot;.l, {:url =&gt; comment_path(comment.commentable_type, comment.commentable_id, comment), :method =&gt; :delete, 500 =&gt; 'alert(\'Sorry, there was a server error\'); return false',  :success =&gt; visual_effect(:fade, &quot;comment_#{comment.id}&quot;), :confirm =&gt; &quot;Are you sure you want to permanently delete this comment&quot;} )
-        
-  / vcard
-  .entry-content= comment.comment
-  / entry-content
-/ ends hentry
+-if comment.user
+  .hentry{:id =&gt; &quot;comment_#{comment.id}&quot;}
+    .vcard.author
+      %h5= link_to image_tag(comment.user.avatar_photo_url(:thumb), :height =&gt; '50', :width =&gt; '50', :alt =&gt; &quot;#{comment.user.login}&quot;), user_path(comment.user), :rel =&gt; 'bookmark', :class =&gt; 'photo', :title =&gt; &quot;#{comment.user.login}'s profile&quot;
+      %ul
+        %li.fn
+          = link_to comment.user.login, user_path(comment.user), :class =&gt; 'url'
+        %li.update
+          %a{&quot;href&quot;=&gt;&quot;#{comment.generate_commentable_url}&quot;, &quot;rel&quot;=&gt;&quot;bookmark&quot;}
+            %abbr.published{&quot;title&quot;=&gt;&quot;#{comment.created_at}&quot;}
+              = comment.created_at.strftime(&quot;%B %d, %Y&quot;)
+        %li.addfriend
+          %a{&quot;href&quot;=&gt;&quot;#&quot;}=&quot;Add as friend&quot;.l
+        -if ( comment.can_be_deleted_by(current_user) )
+          %li.delete=link_to_remote(&quot;Delete&quot;.l, {:url =&gt; comment_path(comment.commentable_type, comment.commentable_id, comment), :method =&gt; :delete, 500 =&gt; 'alert(\'Sorry, there was a server error\'); return false',  :success =&gt; visual_effect(:fade, &quot;comment_#{comment.id}&quot;), :confirm =&gt; &quot;Are you sure you want to permanently delete this comment&quot;} )
+    .entry-content= comment.comment
+
+-else
+  .hentry{:id =&gt; &quot;comment_#{comment.id}&quot;}
+    .vcard.author
+      %h5
+        %a{:href =&gt; '#', :rel =&gt; 'bookmark', :class =&gt; 'photo', :onclick =&gt; 'return false;'}
+          = image_tag(AppConfig.photo['missing_thumb'], :height =&gt; '50', :width =&gt; '50')
+      %ul
+        %li.fn
+          =link_to_unless(comment.author_url.blank?, h(comment.username), h(comment.author_url)){ h(comment.username) }
+        %li.update
+          %a{&quot;href&quot;=&gt;&quot;#{comment.generate_commentable_url}&quot;, &quot;rel&quot;=&gt;&quot;bookmark&quot;}
+            %abbr.published{&quot;title&quot;=&gt;&quot;#{comment.created_at}&quot;}
+              = comment.created_at.strftime(&quot;%B %d, %Y&quot;)
+        -if ( comment.can_be_deleted_by(current_user) )
+          %li.delete=link_to_remote(&quot;Delete&quot;.l, {:url =&gt; comment_path(comment.commentable_type, comment.commentable_id, comment), :method =&gt; :delete, 500 =&gt; 'alert(\'Sorry, there was a server error\'); return false',  :success =&gt; visual_effect(:fade, &quot;comment_#{comment.id}&quot;), :confirm =&gt; &quot;Are you sure you want to permanently delete this comment&quot;} )
+    .entry-content= comment.comment
 
 - highlighted ||= nil
 - if highlighted
   %script{&quot;type&quot;=&gt;&quot;text/javascript&quot;}
     =visual_effect :highlight, &quot;comment_#{comment.id}&quot;, :duration =&gt; 1 
+</diff>
      <filename>app/views/comments/_comment.html.haml</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,4 @@
-- if current_user
+- if current_user || AppConfig.allow_anonymous_commenting
   %script{&quot;type&quot;=&gt;&quot;text/javascript&quot;}
     function scrollToNewestComment(){
     loc = document.location.toString();
@@ -11,10 +11,28 @@
     document.location.href = loc;
     }
   .errors
-  - form_remote_for(:comment, :loading =&gt; &quot;$$('div#comments div.errors')[0].innerHTML = ''; $('comment_spinner').show();&quot;, :before =&gt; &quot;tinyMCE.activeEditor.save();&quot;, :url =&gt; comments_url(Inflector.underscore(commentable.class), commentable.id ), 500 =&gt; &quot;$$('div#comments div.errors')[0].innerHTML = request.responseText; return false;&quot;, :success =&gt; &quot;new Insertion.#{commentable.class.to_s.eql?('User') ? 'After': 'After' }('newest_comment', request.responseText); tinyMCE.activeEditor.setContent(\'\'); scrollToNewestComment();&quot;, :complete =&gt; &quot;$('comment_spinner').hide();&quot;, :html =&gt; {:class =&gt; &quot;MainForm&quot;}) do |f|
+  - form_remote_for(:comment, :loading =&gt; &quot;$$('div#comments div.errors')[0].innerHTML = ''; $('comment_spinner').show();&quot;, :before =&gt; &quot;tinyMCE.activeEditor.save();&quot;, :url =&gt; comments_url(Inflector.underscore(commentable.class), commentable.id ), 500 =&gt; &quot;$$('div#comments div.errors')[0].update(request.responseText);new Effect.Highlight($$('div#comments div.errors')[0]); return false;&quot;, :success =&gt; &quot;new Insertion.#{commentable.class.to_s.eql?('User') ? 'After': 'After' }('newest_comment', request.responseText); tinyMCE.activeEditor.setContent(\'\'); scrollToNewestComment();&quot;, :complete =&gt; &quot;$('comment_spinner').hide(); if($('dynamic_recaptcha')){Recaptcha.create('#{AppConfig.recaptcha_pub_key}', $('dynamic_recaptcha') )}&quot;, :html =&gt; {:class =&gt; &quot;MainForm&quot;}) do |f|
     %label
       %em=&quot;(2000 character limit)&quot;.l :comment_character_limit
     = text_area :comment, :comment, {:size =&gt; &quot;86x5&quot;, :class =&gt; &quot;rich_text_editor&quot;}
+    
+    -unless logged_in?
+      .right{:style =&gt; 'margin-top:2em;'}= recaptcha_tags :ajax =&gt; true
+      
+      %label{&quot;for&quot;=&gt;&quot;comment[author_name&quot;}
+        =&quot;Name:&quot;.l
+        %em=&quot;(#{'Optional'.l})&quot;
+      = f.text_field :author_name, :size =&gt; 35
+      %label{&quot;for&quot;=&gt;&quot;comment[author_email&quot;}
+        =&quot;#{'E-mail'.l}:&quot;
+        %em=&quot;(#{&quot;Required; won't be shown on site&quot;.l})&quot;
+      = f.text_field :author_email, :size =&gt; 35        
+      %label{&quot;for&quot;=&gt;&quot;comment[author_email&quot;}
+        =&quot;Web site (include http://):&quot;.l
+        %em=&quot;(#{'Optional'.l})&quot;
+      = f.text_field :author_url, :size =&gt; 35        
+
+    
     %p
       = submit_tag &quot;Add Comment&quot;.l
       = image_tag 'spinner.gif', :plugin =&gt; &quot;community_engine&quot;, :style =&gt; 'display:none;', :id =&gt; 'comment_spinner'</diff>
      <filename>app/views/comments/_comment_form.html.haml</filename>
    </modified>
    <modified>
      <diff>@@ -1 +1,5 @@
-page.replace_html :dynamic_images_list, :partial =&gt; 'manage_photos', :locals =&gt; { :photos =&gt; @photos }
\ No newline at end of file
+if logged_in?
+  page.replace_html :dynamic_images_list, :partial =&gt; 'manage_photos', :locals =&gt; { :photos =&gt; @photos }
+else
+  page &lt;&lt; &quot;mcTabs.displayTab('general_tab','general_panel'); $('dynamic_select_tab').hide(); $('image_upload_form').up('fieldset').hide();&quot;
+end
\ No newline at end of file</diff>
      <filename>app/views/photos/manage_photos.rjs</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 Hey &lt;%= @user.login %&gt;!
 
-&lt;%= @commenter.login %&gt; has something to say about you! Use this link to see:
+&lt;%= @comment.username %&gt; has something to say about you! Use this link to see:
 
 &lt;%= @url %&gt; 
 </diff>
      <filename>app/views/user_notifier/comment_notice.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 Hey &lt;%= @user.login %&gt;!
 
-&lt;%= @commenter.login %&gt; has added a comment to a &lt;%= @comment.commentable_type %&gt; 
+&lt;%= @comment.username %&gt; has added a comment to a &lt;%= @comment.commentable_type %&gt; 
 that you commented on. Use this link to check it out:
 
 &lt;%= @url %&gt; </diff>
      <filename>app/views/user_notifier/follow_up_comment_notice.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -12,7 +12,7 @@ AppConfig.default_mce_options = {
   :cleanup_on_startup =&gt; true,  
   :convert_fonts_to_spans =&gt; true,
   :theme_advanced_resize_horizontal =&gt; false,
-  :theme_advanced_buttons1 =&gt; %w{bold italic underline separator justifyleft justifycenter justifyright indent outdent separator bullist numlist separator link unlink image media separator undo redo help code},
+  :theme_advanced_buttons1 =&gt; %w{bold italic underline separator justifyleft justifycenter justifyright indent outdent separator bullist numlist separator link unlink image media separator undo redo code},
   :theme_advanced_buttons2 =&gt; [],
   :theme_advanced_buttons3 =&gt; [],
   :plugins =&gt; %w{media preview curblyadvimage inlinepopups safari},
@@ -33,7 +33,7 @@ AppConfig.simple_mce_options = {
   :theme_advanced_statusbar_location =&gt; &quot;bottom&quot;, 
   :editor_deselector =&gt; &quot;mceNoEditor&quot;,
   :theme_advanced_resize_horizontal =&gt; false,  
-  :theme_advanced_buttons1 =&gt; %w{bold italic underline separator bullist numlist separator link unlink image separator help},
+  :theme_advanced_buttons1 =&gt; %w{bold italic underline separator bullist numlist separator link unlink image},
   :theme_advanced_buttons2 =&gt; [],
   :theme_advanced_buttons3 =&gt; [],
   :plugins =&gt; %w{inlinepopups safari curblyadvimage}</diff>
      <filename>engine_config/initializers/mce_options.rb</filename>
    </modified>
    <modified>
      <diff>@@ -17,7 +17,7 @@ all_users_tagged: All users tagged {tag_name}
 #en: comments_count: &quot;{count} pluralize{{count}, comment}&quot;
 comments_count: &quot;{count} pluralize{{count}, comment}&quot;
 #en: comment_save_error: Your comment couldn't be saved.
-comment_save_error: &quot;Your comment couldn't be saved.&quot;
+comment_save_error: &quot;Your comment couldn't be saved. {error}&quot;
 #en: community_tagline: CommunityEngine Rocks!
 community_tagline: CommunityEngine Rocks!
 #en: create: Create</diff>
      <filename>lang/ui/en-US.yml</filename>
    </modified>
    <modified>
      <diff>@@ -76,6 +76,15 @@ class CommentsControllerTest &lt; Test::Unit::TestCase
     end
     assert_response :redirect    
   end
+
+  def test_should_create_post_comment_anonymously
+    AppConfig.allow_anonymous_commenting = true
+    assert_difference Comment, :count, 1 do
+      create_post_comment(:comment =&gt; {:author_email =&gt; 'foor@bar.com'})      
+    end
+    assert_response :redirect    
+  end
+
   
   def test_should_destroy_post_comment
     login_as :quentin</diff>
      <filename>test/functional/comments_controller_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>81e1e33a373f0ba910c01b53f4d7e42a8be95df5</id>
    </parent>
  </parents>
  <author>
    <name>Bruno Bornsztein</name>
    <email>bruno@otis.local</email>
  </author>
  <url>http://github.com/bborn/communityengine/commit/c88f1b54bb227270bab96dd8ccfcbe6968879c5d</url>
  <id>c88f1b54bb227270bab96dd8ccfcbe6968879c5d</id>
  <committed-date>2008-08-21T12:11:09-07:00</committed-date>
  <authored-date>2008-08-21T12:11:09-07:00</authored-date>
  <message>added support for anonymous commenting and recaptcha for comments</message>
  <tree>b83ecb18f5971757d2eb997acd03a86e52e7c31c</tree>
  <committer>
    <name>Bruno Bornsztein</name>
    <email>bruno@otis.local</email>
  </committer>
</commit>
