Skip to content
Browse files

merged edge into master

  • Loading branch information...
2 parents 705e559 + 9fff611 commit 6236d919ef7ee299d9c7223a0e9fa95f3e7e390e @bborn committed
Showing with 2,473 additions and 1,084 deletions.
  1. +15 −2 CHANGELOG
  2. +27 −1 README.markdown
  3. +22 −0 UPGRADING.markdown
  4. +1 −1 about.yml
  5. +40 −0 app/controllers/admin_controller.rb
  6. +4 −1 app/controllers/ads_controller.rb
  7. +1 −2 app/controllers/albums_controller.rb
  8. +7 −1 app/controllers/base_controller.rb
  9. +1 −1 app/controllers/categories_controller.rb
  10. +1 −1 app/controllers/comment_sweeper.rb
  11. +36 −5 app/controllers/comments_controller.rb
  12. +0 −1 app/controllers/friendships_controller.rb
  13. +7 −3 app/controllers/homepage_features_controller.rb
  14. +27 −16 app/controllers/messages_controller.rb
  15. +52 −0 app/controllers/password_resets_controller.rb
  16. +1 −1 app/controllers/photos_controller.rb
  17. +1 −1 app/controllers/post_sweeper.rb
  18. +2 −2 app/controllers/posts_controller.rb
  19. +24 −16 app/controllers/sb_posts_controller.rb
  20. +0 −4 app/controllers/tags_controller.rb
  21. +3 −3 app/controllers/topics_controller.rb
  22. +39 −23 app/controllers/users_controller.rb
  23. +3 −3 app/helpers/base_helper.rb
  24. +0 −16 app/helpers/posts_helper.rb
  25. +1 −1 app/helpers/users_helper.rb
  26. +17 −7 app/models/comment.rb
  27. +1 −1 app/models/homepage_feature.rb
  28. +26 −8 app/models/message.rb
  29. +33 −0 app/models/message_thread.rb
  30. +0 −15 app/models/photo.rb
  31. +26 −4 app/models/sb_post.rb
  32. +28 −4 app/models/user.rb
  33. +6 −4 app/models/user_notifier.rb
  34. +22 −5 app/views/admin/comments.html.haml
  35. +1 −1 app/views/admin/messages.html.haml
  36. +63 −33 app/views/admin/users.html.haml
  37. +38 −27 app/views/ads/index.html.haml
  38. +1 −1 app/views/clippings/show.html.haml
  39. +5 −0 app/views/comments/_comment.html.haml
  40. +12 −11 app/views/comments/_comment_form.html.haml
  41. +29 −0 app/views/comments/_edit_form.html.haml
  42. +7 −0 app/views/comments/edit.js.rjs
  43. +9 −0 app/views/comments/update.js.rjs
  44. +1 −1 app/views/events/show.html.haml
  45. +3 −3 app/views/friendships/index.xml.builder
  46. +0 −35 app/views/homepage_features/edit.html.erb
  47. +31 −0 app/views/homepage_features/edit.html.haml
  48. +0 −34 app/views/homepage_features/new.html.erb
  49. +30 −0 app/views/homepage_features/new.html.haml
  50. +0 −33 app/views/homepage_features/show.html.erb
  51. +29 −0 app/views/homepage_features/show.html.haml
  52. +2 −2 app/views/layouts/application.html.haml
  53. +2 −2 app/views/layouts/beta.html.haml
  54. +6 −2 app/views/messages/_form.html.haml
  55. +13 −13 app/views/messages/_inbox.html.haml
  56. +2 −1 app/views/messages/_sent.html.haml
  57. +4 −3 app/views/messages/index.html.haml
  58. +21 −18 app/views/messages/show.html.haml
  59. +16 −0 app/views/password_resets/edit.html.haml
  60. +1 −1 app/views/{users/forgot_password.html.haml → password_resets/new.html.haml}
  61. +2 −6 app/views/photo_manager/index.html.haml
  62. +1 −1 app/views/photos/_show_image_list.html.haml
  63. +19 −20 app/views/photos/index.html.haml
  64. +70 −69 app/views/photos/new.html.haml
  65. +1 −1 app/views/photos/show.html.haml
  66. +1 −3 app/views/posts/_favorited_post.html.haml
  67. +6 −0 app/views/posts/edit.html.haml
  68. +5 −0 app/views/posts/new.html.haml
  69. +1 −1 app/views/posts/show.html.haml
  70. +2 −2 app/views/{layouts → sb_posts}/_post.xml.builder
  71. +37 −0 app/views/sb_posts/_reply_form.html.haml
  72. +35 −0 app/views/sb_posts/_sb_post.html.haml
  73. +13 −0 app/views/sb_posts/create.js.rjs
  74. +1 −1 app/views/sb_posts/index.xml.builder
  75. +6 −0 app/views/shared/_admin_nav.html.haml
  76. +4 −4 app/views/shared/_header.html.haml
  77. +2 −2 app/views/sitemap/index.xml.builder
  78. +5 −4 app/views/tags/index.html.haml
  79. +7 −3 app/views/tags/show.html.haml
  80. +7 −7 app/views/topics/edit.html.haml
  81. +11 −43 app/views/topics/show.html.haml
  82. +2 −1 app/views/topics/show.xml.builder
  83. 0 app/views/user_notifier/{activation.html.erb → activation.erb}
  84. 0 app/views/user_notifier/{activation.fr.html.erb → activation.fr.erb}
  85. 0 app/views/user_notifier/{comment_notice.html.erb → comment_notice.erb}
  86. 0 app/views/user_notifier/{comment_notice.fr.html.erb → comment_notice.fr.erb}
  87. +1 −1 app/views/user_notifier/{follow_up_comment_notice.html.erb → follow_up_comment_notice.erb}
  88. 0 app/views/user_notifier/{follow_up_comment_notice.fr.html.erb → follow_up_comment_notice.fr.erb}
  89. 0 ...user_notifier/{follow_up_comment_notice_anonymous.rhtml → follow_up_comment_notice_anonymous.erb}
  90. 0 ...otifier/{follow_up_comment_notice_anonymous.fr.rhtml → follow_up_comment_notice_anonymous.fr.erb}
  91. 0 app/views/user_notifier/{forgot_username.html.erb → forgot_username.erb}
  92. 0 app/views/user_notifier/{forgot_username.fr.html.erb → forgot_username.fr.erb}
  93. 0 app/views/user_notifier/{friendship_accepted.rhtml → friendship_accepted.erb}
  94. 0 app/views/user_notifier/{friendship_accepted.fr.html.erb → friendship_accepted.fr.erb}
  95. 0 app/views/user_notifier/{friendship_request.html.erb → friendship_request.erb}
  96. 0 app/views/user_notifier/{friendship_request.fr.html.erb → friendship_request.fr.erb}
  97. +7 −1 app/views/user_notifier/{message_notification.html.erb → message_notification.erb}
  98. 0 app/views/user_notifier/{message_notification.fr.html.erb → message_notification.fr.erb}
  99. +2 −2 app/views/user_notifier/{new_forum_post_notice.html.erb → new_forum_post_notice.erb}
  100. 0 app/views/user_notifier/{new_forum_post_notice.fr.html.erb → new_forum_post_notice.fr.erb}
  101. +10 −0 app/views/user_notifier/password_reset_instructions.erb
  102. +3 −2 app/views/user_notifier/{reset_password.fr.html.erb → password_reset_instructions.fr.erb}
  103. 0 app/views/user_notifier/{post_recommendation.html.erb → post_recommendation.erb}
  104. 0 app/views/user_notifier/{post_recommendation.fr.html.erb → post_recommendation.fr.erb}
  105. +0 −7 app/views/user_notifier/reset_password.html.erb
  106. 0 app/views/user_notifier/{signup_invitation.html.erb → signup_invitation.erb}
  107. 0 app/views/user_notifier/{signup_invitation.fr.html.erb → signup_invitation.fr.erb}
  108. 0 app/views/user_notifier/{signup_notification.html.erb → signup_notification.erb}
  109. 0 app/views/user_notifier/{signup_notification.fr.html.erb → signup_notification.fr.erb}
  110. +1 −1 app/views/users/_user.html.haml
  111. +13 −0 app/views/users/_user.xml.haml
  112. +43 −41 app/views/users/edit_account.html.haml
  113. +4 −0 app/views/users/index.xml.haml
  114. +3 −4 app/views/users/show.html.haml
  115. +9 −0 app/views/users/show.xml.haml
  116. +13 −1 app/views/users/welcome_invite.html.haml
  117. +4 −3 community_engine_setup_template.rb
  118. +7 −3 config/application.yml
  119. +8 −5 config/desert_routes.rb
  120. +5 −0 config/initializers/rakismet.rb
  121. +19 −0 db/migrate/074_add_threading_to_messages.rb
  122. +16 −0 db/migrate/075_add_anonymous_forum_posting.rb
  123. +9 −0 db/migrate/076_add_comment_notification_toggle.rb
  124. +55 −0 db/sample/users.rb
  125. +4 −4 lang/ui/de-DE.yml
  126. +26 −10 lang/ui/en.yml
  127. +29 −7 lang/ui/es-AR.yml
  128. +10 −3 lang/ui/es-ES.yml
  129. +3 −3 lang/ui/es-MX.yml
  130. +3 −3 lang/ui/fr-FR.yml
  131. +3 −3 lang/ui/ja-JP.yml
  132. +1 −1 lang/ui/ru-RU.yml
  133. +1 −1 lang/ui/sr-CP.yml
  134. +4 −4 lang/ui/sv-SE.yml
  135. +3 −3 lib/authenticated_system.rb
  136. +1 −1 lib/community_engine.rb
  137. +1 −1 plugins/acts_as_publishable/lib/acts_as_publishable.rb
  138. +3 −0 plugins/attachment_fu/README
  139. +3 −0 plugins/attachment_fu/amazon_s3.yml.tpl
  140. +3 −0 plugins/attachment_fu/lib/technoweenie/attachment_fu.rb
  141. +51 −1 plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/s3_backend.rb
  142. +744 −345 plugins/mimetype-fu/lib/mime_types.yml
  143. +22 −14 plugins/mimetype-fu/lib/mimetype_fu.rb
  144. +10 −0 plugins/tiny_mce/assets/javascripts/tiny_mce/plugins/table/js/table.js
  145. +5 −1 plugins/tiny_mce/lib/tiny_mce_helper.rb
  146. +4 −1 plugins/tiny_mce/tiny_mce_options.yml
  147. BIN public/images/icons/bad-flag.png
  148. +1 −1 public/javascripts/application.js
  149. +10 −0 public/javascripts/tiny_mce/plugins/table/js/table.js
  150. +3 −0 public/stylesheets/screen.css
  151. +4 −4 sample_files/sample_themes/dappled/views/shared/_header.html.haml
  152. +26 −11 tasks/community_engine_tasks.rake
  153. +2 −2 test/fixtures/metro_areas.yml
  154. +21 −2 test/functional/admin_controller_test.rb
  155. +35 −0 test/functional/comments_controller_test.rb
  156. +4 −14 test/functional/messages_controller_test.rb
  157. +34 −0 test/functional/password_reset_controller_test.rb
  158. +26 −5 test/functional/sb_posts_controller_test.rb
  159. +24 −18 test/functional/users_controller_test.rb
  160. +2 −2 test/test_helper.rb
  161. +11 −0 test/unit/comment_test.rb
  162. +4 −0 test/unit/message_test.rb
  163. +14 −2 test/unit/post_test.rb
  164. +29 −0 test/unit/sb_post_test.rb
  165. +9 −0 test/unit/topic_test.rb
  166. +3 −2 test/unit/user_notifier_test.rb
  167. +7 −2 test/unit/user_test.rb
View
17 CHANGELOG
@@ -1,6 +1,19 @@
= TODO
-* haml/sass propagation (desert)
-* upgrade documentation/rake tasks to fix old migrations
+
+=1.2.1
+* Anonymous forum replies
+* Turn comment notifications on or off by post
+* Allow admins/mods to edit comments (they can already delete them)
+* Fix security vulnerability in AuthenticatedSystem
+
+=1.2.0
+* Threaded private messages
+* Clear cache link in admin dashboard
+* Add support for using rakismet gem to check comments for spam (see README for instructions)
+
+=1.1.0
+* fixed time_ago formatting problem on user/index
+* user Authlogic's perishable token for doing password resets (instead of sending them a password)
= 1.0.4.2
View
28 README.markdown
@@ -1,3 +1,4 @@
+CommunityEngine [v1.2.1]
** Looking for the Rails 3.1 version? You want the [Rails3 branch](https://github.com/bborn/communityengine/tree/rails3).
CommunityEngine [v1.0.4.2]
@@ -19,6 +20,7 @@ Requirements:
ri_cal
authlogic
searchlogic
+ rakismet
aws-s3 (if using s3 for photos)
Getting CommunityEngine Running
@@ -26,7 +28,7 @@ Getting CommunityEngine Running
SHORT VERSION:
- rails your_app_name -m http://github.com/bborn/communityengine/raw/master/community_engine_setup_template.rb
+ rails your_app_name -m https://raw.github.com/bborn/communityengine/edge/community_engine_setup_template.rb
LONG VERSION:
@@ -77,6 +79,7 @@ LONG VERSION:
config.gem 'icalendar'
config.gem 'authlogic'
config.gem 'searchlogic'
+ config.gem 'rakismet'
config.action_controller.session = {
:key => '_your_app_session',
@@ -228,6 +231,29 @@ Note, this will affect the look and feel of buttons. You can highlight what is l
For more, see /lang/readme.txt.
+Spam Control
+------------
+
+Spam sucks. Most likely, you'll need to implement some custom solution to control spam on your site, but CE offers a few tools to help with the basics.
+
+ReCaptcha: to allow non-logged-in commenting and use [ReCaptcha](http://recaptcha.net/) to ensure robots aren't submitting comments to your site, just add the following lines to your `application.yml`:
+
+ allow_anonymous_commenting: true
+ recaptcha_pub_key: YOUR_PUBLIC_KEY
+ recaptcha_priv_key: YOUR_PRIVATE_KEY
+
+You can also require recaptcha on signup (to prevent automated signups) by adding this in your `application.yml` (you'll still need to add your ReCaptcha keys):
+
+ require_captcha_on_signup: true
+
+Akismet: Unfortunately, bots aren't the only ones submitting spam; humans do it to. [Akismet](http://akismet.com/) is a great collaborative spam filter from the makers of Wordpress, and you can use it to check for spam comments by adding one line to your `application.yml`:
+
+ akismet_key: 4bfd15b0ea46
+
+(If you do this, make sure you are requiring the `rakismet` gem in `environment.rb`)
+
+
+
Other notes
-----------
View
22 UPGRADING.markdown
@@ -1,3 +1,25 @@
+Upgrading to v1.2.1
+===================
+Run `ruby script/generate plugin_migration`
+Run `rake db:migrate`
+Run `rake test && rake community_engine:test`
+
+
+Upgrading to v1.2.0
+===================
+Run `ruby script/generate plugin_migration`
+Run `rake db:migrate`
+Run `rake test && rake community_engine:test`
+To migrate existing private messages to the new threaded message format, run `rake community_engine:add_threads_to_existing_messages` on your production server (CAREFUL: make backups first!)
+
+
+Upgrading to v1.1.0
+=====================
+Run `ruby script/generate plugin_migration`
+Run `rake db:migrate`
+Run `rake test && rake community_engine:test`
+
+
Upgrading to v1.0.4.2
=====================
Run `rake gems:install`
View
2 about.yml
@@ -4,4 +4,4 @@ homepage: http://www.missingmethod.com
summary: A social networking engine
description: Adds basic social networking capabilities to your existing application, including users, blogs, photos, clippings, favorites, and more.
license: MIT
-version: 1.0.4.2
+version: 1.2.1
View
40 app/controllers/admin_controller.rb
@@ -1,6 +1,21 @@
class AdminController < BaseController
before_filter :admin_required
+ def clear_cache
+ case Rails.cache
+ when ActiveSupport::Cache::FileStore
+ dir = Rails.cache.cache_path
+ unless dir == Rails.public_path
+ FileUtils.rm_r(Dir.glob(dir+"/*")) rescue Errno::ENOENT
+ Rails.logger.info("Cache directory fully swept.")
+ end
+ flash[:notice] = :cache_cleared.l
+ else
+ Rails.logger.warn("Cache not swept: you must override AdminController#clear_cache to support #{Rails.cache}")
+ end
+ redirect_to admin_dashboard_path and return
+ end
+
def contests
@contests = Contest.find(:all)
@@ -27,8 +42,22 @@ def users
if params['email']
cond.email =~ "%#{params['email']}%"
end
+ if params['featured']
+ cond.featured_writer == true
+ end
+ if params['id']
+ cond.id == params['id']
+ end
+
@users = User.recent.find(:all, :page => {:current => params[:page], :size => 100}, :conditions => cond.to_sql)
+
+ respond_to do |format|
+ format.html
+ format.xml {
+ render :xml => @users.to_xml(:except => [ :password, :crypted_password, :single_access_token, :perishable_token, :password_salt, :persistence_token ])
+ }
+ end
end
def comments
@@ -51,4 +80,15 @@ def deactivate_user
redirect_to :action => :users
end
+ def subscribers
+ @users = User.find(:all, :conditions => ["notify_community_news = ? AND users.activated_at IS NOT NULL", (params[:unsubs] ? false : true)])
+
+ respond_to do |format|
+ format.xml {
+ render :xml => @users.to_xml(:only => [:login, :email])
+ }
+ end
+
+ end
+
end
View
5 app/controllers/ads_controller.rb
@@ -1,11 +1,14 @@
class AdsController < BaseController
+
before_filter :login_required
before_filter :admin_required
# GET /ads
# GET /ads.xml
def index
- @ads = Ad.find(:all)
+ @search = Ad.search(params[:search])
+ @search.order ||= :descend_by_created_at
+ @ads = @search.find(:all, :page => {:current => params[:page], :size => 15})
respond_to do |format|
format.html # index.rhtml
View
3 app/controllers/albums_controller.rb
@@ -14,8 +14,7 @@ class AlbumsController < BaseController
def show
@album = Album.find(params[:id])
update_view_count(@album) if current_user && current_user.id != @album.user_id
- @album_photos = Photo.find(:all, :page => { :start => 1, :current => params[:page], :size => 10 },
- :conditions => ['album_id = ? AND parent_id IS NULL', params[:id]])
+ @album_photos = @album.photos.find(:all, :page => { :start => 1, :current => params[:page], :size => 10 })
respond_to do |format|
format.html # show.html.erb
View
8 app/controllers/base_controller.rb
@@ -9,6 +9,7 @@ class BaseController < ApplicationController
skip_before_filter :verify_authenticity_token, :only => :footer_content
helper_method :commentable_url
before_filter :initialize_header_tabs
+ before_filter :initialize_admin_tabs
caches_action :site_index, :footer_content, :if => Proc.new{|c| c.cache_action? }
def cache_action?
@@ -126,7 +127,7 @@ def get_additional_homepage_data
@homepage_features = HomepageFeature.find_features
@homepage_features_data = @homepage_features.collect {|f| [f.id, f.public_filename(:large) ] }
- @active_users = User.active.find_by_activity({:limit => 5, :require_avatar => false})
+ @active_users = User.find_by_activity({:limit => 5, :require_avatar => false})
@featured_writers = User.find_featured
@featured_posts = Post.find_featured
@@ -164,5 +165,10 @@ def initialize_header_tabs
# Usage: @header_tabs << {:name => "My tab", :url => my_tab_path, :section => 'my_tab_section' }
@header_tabs = []
end
+ def initialize_admin_tabs
+ # This hook allows plugins or host apps to easily add tabs to the admin nav by adding to the @admin_nav_links array
+ # Usage: @admin_nav_links << {:name => "My link", :url => my_link_path, }
+ @admin_nav_links = []
+ end
end
View
2 app/controllers/categories_controller.rb
@@ -29,7 +29,7 @@ def show
@popular_posts = @category.posts.find(:all, :limit => 10, :order => "view_count DESC")
@popular_polls = Poll.find_popular_in_category(@category)
- @rss_title = "#{AppConfig.community_name}: #{@category.name} "+:posts.l
+ @rss_title = "#{AppConfig.community_name}: #{@category.name} " + :posts.l
@rss_url = category_path(@category, :format => :rss)
@active_users = User.find(:all,
View
2 app/controllers/comment_sweeper.rb
@@ -21,7 +21,7 @@ def expire_cache_for(record)
expire_action :controller => 'base', :action => 'footer_content'
if record.commentable_type.eql?('Post')
- expire_action :controller => 'posts', :action => 'show', :id => record.commentable , :user_id => record.commentable.user
+ expire_action :controller => 'posts', :action => 'show', :id => record.commentable.to_param , :user_id => record.commentable.user.to_param
if Post.find_recent(:limit => 16).include?(record.commentable)
# Expire the home page
View
41 app/controllers/comments_controller.rb
@@ -1,22 +1,43 @@
class CommentsController < BaseController
helper :comments
before_filter :login_required, :except => [:index, :unsubscribe]
- before_filter :admin_or_moderator_required, :only => [:delete_selected]
+ before_filter :admin_or_moderator_required, :only => [:delete_selected, :edit, :update]
if AppConfig.allow_anonymous_commenting
skip_before_filter :verify_authenticity_token, :only => [:create] #because the auth token might be cached anyway
skip_before_filter :login_required, :only => [:create]
end
- uses_tiny_mce(:only => [:index]) do
+ uses_tiny_mce(:only => [:index, :edit, :update]) do
AppConfig.simple_mce_options
end
cache_sweeper :comment_sweeper, :only => [:create, :destroy]
- def index
- @commentable = comment_type.constantize.find(comment_id)
+ def edit
+ @comment = Comment.find(params[:id])
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def update
+ @comment = Comment.find(params[:id])
+ @comment.update_attributes(params[:comment])
+ @comment.save!
+ respond_to do |format|
+ format.js
+ end
+ end
+
+
+ def index
+ if is_valid_comment_type?(comment_type)
+ @commentable = comment_type.constantize.find(comment_id)
+ else
+ redirect_to home_path and return
+ end
#don't use the get_type, as we want the specific case where the user typed /User/username/comments
redirect_to user_comments_path(params[:commentable_id]) and return if (params[:commentable_type] && params[:commentable_type].camelize == "User")
@@ -104,6 +125,9 @@ def create
def destroy
@comment = Comment.find(params[:id])
if @comment.can_be_deleted_by(current_user) && @comment.destroy
+ if params[:spam] && AppConfig.akismet_key
+ @comment.spam!
+ end
flash.now[:notice] = :the_comment_was_deleted.l
else
flash.now[:error] = :comment_could_not_be_deleted.l
@@ -122,6 +146,7 @@ def delete_selected
if params[:delete]
params[:delete].each { |id|
comment = Comment.find(id)
+ comment.spam! if params[:spam] && AppConfig.akismet_key
comment.destroy if comment.can_be_deleted_by(current_user)
}
end
@@ -146,6 +171,12 @@ def comment_type
return "User" unless params[:commentable_type]
params[:commentable_type].camelize
end
+
+ def is_valid_comment_type?(type)
+ tables = ActiveRecord::Base.connection.tables
+ models = tables.collect(&:classify)
+ models.include?(type)
+ end
def comment_id
params[:commentable_id] || params[:user_id]
@@ -156,7 +187,7 @@ def comment_link
end
def full_comment_link
- "#{application_url}#{comment_link}"
+ "#{home_url}#{comment_link}"
end
def comment_rss_link
View
1 app/controllers/friendships_controller.rb
@@ -105,7 +105,6 @@ def create
format.js { render( :inline => :requested_friendship_with.l+" #{@friendship.friend.login}." ) }
else
flash.now[:error] = :friendship_could_not_be_created.l
- @users = User.find(:all)
format.html { redirect_to user_friendships_path(@user) }
format.js { render( :inline => "Friendship request failed." ) }
end
View
10 app/controllers/homepage_features_controller.rb
@@ -1,5 +1,5 @@
class HomepageFeaturesController < BaseController
- uses_tiny_mce(:only => [:new, :edit ]) do
+ uses_tiny_mce(:only => [:new, :edit, :create, :update ]) do
AppConfig.default_mce_options
end
@@ -8,8 +8,12 @@ class HomepageFeaturesController < BaseController
# GET /homepage_features
# GET /homepage_features.xml
def index
- @homepage_features = HomepageFeature.find(:all, :conditions => ["parent_id IS NULL"], :order => "created_at desc")
-
+
+ @search = HomepageFeature.search(params[:search])
+ @search.order ||= :descend_by_created_at
+
+ @homepage_features = @search.find(:all, :conditions => ["parent_id IS NULL"], :page => {:current => params[:page], :size => 100})
+
respond_to do |format|
format.html # index.rhtml
end
View
43 app/controllers/messages_controller.rb
@@ -14,44 +14,41 @@ def index
if params[:mailbox] == "sent"
@messages = @user.sent_messages.find(:all, :page => {:current => params[:page], :size => 20})
else
- @messages = @user.received_messages.find(:all, :page => {:current => params[:page], :size => 20})
+ @messages = @user.message_threads_as_recipient.find(:all, :page => {:current => params[:page], :size => 20}, :order => 'updated_at DESC')
end
end
def show
@message = Message.read(params[:id], current_user)
- @reply = Message.new_reply(@user, @message, params)
+ @message_thread = MessageThread.for(@message, (admin? ? @message.recipient : current_user ))
+ @reply = Message.new_reply(@user, @message_thread, params)
end
def new
if params[:reply_to]
in_reply_to = Message.find_by_id(params[:reply_to])
+ message_thread = MessageThread.for(in_reply_to, current_user)
end
- @message = Message.new_reply(@user, in_reply_to, params)
+ @message = Message.new_reply(@user, message_thread, params)
end
def create
messages = []
if params[:message][:to].blank?
- # If 'to' field is empty, call validations to catch other
+ # If 'to' field is empty, call validations to catch other errors
@message = Message.new(params[:message])
@message.valid?
render :action => :new and return
else
- # If 'to' field isn't empty then make sure each recipient is valid
- params[:message][:to].split(',').uniq.each do |to|
- @message = Message.new(params[:message])
- @message.recipient = User.find_by_login(to.strip)
- @message.sender = @user
- unless @message.valid?
- render :action => :new and return
- else
- messages << @message
- end
+ @message = Message.new(params[:message])
+ @message.recipient = User.find_by_login(params[:message][:to].strip)
+ @message.sender = @user
+ unless @message.valid?
+ render :action => :new and return
+ else
+ @message.save!
end
- # If all messages are valid then send messages
- messages.each {|msg| msg.save!}
flash[:notice] = :message_sent.l
redirect_to user_messages_path(@user) and return
end
@@ -70,6 +67,20 @@ def delete_selected
end
end
+ def delete_message_threads
+ if request.post?
+ if params[:delete]
+ params[:delete].each { |id|
+ message_thread = MessageThread.find_by_id_and_recipient_id(id, @user.id)
+ message_thread.destroy if message_thread
+ }
+ flash[:notice] = :messages_deleted.l
+ end
+ redirect_to user_messages_path(@user)
+ end
+
+ end
+
private
def find_user
@user = User.find(params[:user_id])
View
52 app/controllers/password_resets_controller.rb
@@ -0,0 +1,52 @@
+class PasswordResetsController < BaseController
+
+ before_filter :require_no_user
+ before_filter :load_user_using_perishable_token, :only => [ :edit, :update ]
+
+ def new
+ end
+
+ def create
+ @user = User.find_by_email(params[:email])
+ if @user
+ @user.deliver_password_reset_instructions!
+
+ flash[:info] = :your_password_reset_instructions_have_been_emailed_to_you.l
+
+ redirect_to login_path
+ else
+ flash[:error] = :sorry_we_dont_recognize_that_email_address.l
+
+ render :action => :new
+ end
+ end
+
+ def edit
+ end
+
+ def update
+ @user.password = params[:password]
+ @user.password_confirmation = params[:password_confirmation]
+
+ if @user.save
+ flash[:notice] = :your_changes_were_saved.l
+
+ redirect_to dashboard_user_path(@user)
+ else
+ flash[:error] = @user.errors.full_messages.to_sentence
+ render :action => :edit
+ end
+ end
+
+
+ private
+
+ def load_user_using_perishable_token
+ @user = User.find_using_perishable_token(params[:id])
+ unless @user
+ flash[:error] = :an_error_occurred.l
+ redirect_to login_path
+ end
+ end
+
+end
View
2 app/controllers/photos_controller.rb
@@ -29,7 +29,7 @@ def index
cond.append ['tags.name = ?', params[:tag_name]]
end
- @photos = Photo.recent.find(:all, :conditions => cond.to_sql, :include => :tags, :page => {:current => params[:page]})
+ @photos = Photo.recent.find(:all, :conditions => cond.to_sql, :include => :tags, :page => {:current => params[:page], :size => 30})
@tags = Photo.tag_counts :conditions => { :user_id => @user.id }, :limit => 20
View
2 app/controllers/post_sweeper.rb
@@ -31,6 +31,6 @@ def expire_cache_for(record)
expire_action(:controller => 'categories', :action => 'show')
# Also expire the show pages, in case we just edited a blog entry
- expire_action(:controller => 'posts', :action => 'show', :id => record.to_param)
+ expire_action(:controller => 'posts', :action => 'show', :id => record.to_param, :user_id => record.user.to_param)
end
end
View
4 app/controllers/posts_controller.rb
@@ -61,11 +61,12 @@ def index
# GET /posts/1
# GET /posts/1.xml
- def show
+ def show
@rss_title = "#{AppConfig.community_name}: #{@user.login}'s posts"
@rss_url = user_posts_path(@user,:format => :rss)
@post = Post.find(params[:id])
+
@user = @post.user
@is_current_user = @user.eql?(current_user)
@comment = Comment.new(params[:comment])
@@ -78,7 +79,6 @@ def show
@related = Post.find_related_to(@post)
@most_commented = Post.find_most_commented
-
# respond_to do |format|
# format.html
# format.any
View
40 app/controllers/sb_posts_controller.rb
@@ -2,6 +2,12 @@ class SbPostsController < BaseController
before_filter :find_post, :except => [:index, :monitored, :search, :create, :new]
before_filter :login_required, :except => [:index, :search, :show, :monitored]
+ if AppConfig.allow_anonymous_forum_posting
+ skip_before_filter :verify_authenticity_token, :only => [:create] #because the auth token might be cached anyway
+ skip_before_filter :login_required, :only => [:create]
+ end
+
+
uses_tiny_mce(:only => [:edit, :update]) do
AppConfig.default_mce_options
end
@@ -57,29 +63,31 @@ def create
flash[:notice] = :this_topic_is_locked.l
redirect_to(forum_topic_path(:forum_id => params[:forum_id], :id => params[:topic_id]))
end
- format.xml do
- render :text => :this_topic_is_locked.l, :status => 400
- end
end
return
end
+
@forum = @topic.forum
@post = @topic.sb_posts.build(params[:post])
- @post.user = current_user
- @post.save!
- respond_to do |format|
- format.html do
- redirect_to forum_topic_path(:forum_id => params[:forum_id], :id => params[:topic_id], :anchor => @post.dom_id, :page => params[:page] || '1')
+
+ @post.user = current_user if current_user
+ @post.author_ip = request.remote_ip #save the ip address for everyone, just because
+
+ if (logged_in? || verify_recaptcha(@post)) && @post.save
+ respond_to do |format|
+ format.html do
+ redirect_to forum_topic_path(:forum_id => params[:forum_id], :id => params[:topic_id], :anchor => @post.dom_id, :page => params[:page] || '1')
+ end
+ format.js
end
- format.xml { head :created, :location => sb_user_post_url(:forum_id => params[:forum_id], :topic_id => params[:topic_id], :id => @post, :format => :xml) }
- end
- rescue ActiveRecord::RecordInvalid
- flash[:bad_reply] = :please_post_something_at_least.l
- respond_to do |format|
- format.html do
- redirect_to forum_topic_path(:forum_id => params[:forum_id], :id => params[:topic_id], :anchor => 'reply-form', :page => params[:page] || '1')
+ else
+ flash.now[:notice] = @post.errors.full_messages.to_sentence
+ respond_to do |format|
+ format.html do
+ redirect_to forum_topic_path({:forum_id => params[:forum_id], :id => params[:topic_id], :anchor => 'reply-form', :page => (params[:page] || '1')}.merge({:post => params[:post]}))
+ end
+ format.js
end
- format.xml { render :xml => @post.errors.to_xml, :status => 400 }
end
end
View
4 app/controllers/tags_controller.rb
@@ -24,10 +24,6 @@ def index
@clipping_tags = popular_tags(75, ' count DESC', 'Clipping')
end
-
- def manage
- @tags = Tag.find(:all, :order => :name, :page => {:current => params[:page], :size => 20})
- end
def manage
@search = Tag.search(params[:search])
View
6 app/controllers/topics_controller.rb
@@ -2,7 +2,7 @@ class TopicsController < BaseController
before_filter :find_forum_and_topic, :except => :index
before_filter :login_required, :except => [:index, :show]
- uses_tiny_mce(:only => [:show, :new]) do
+ uses_tiny_mce(:only => [:show, :new, :create, :edit, :update]) do
AppConfig.default_mce_options
end
@@ -35,8 +35,8 @@ def show
@posts = @topic.sb_posts.recent.find(:all, :page => {:current => params[:page], :size => 25}, :include => :user)
@voices = @posts.map(&:user)
- @voices.uniq!
- @post = SbPost.new
+ @voices.compact.uniq!
+ @post = SbPost.new(params[:post])
end
format.xml do
render :xml => @topic.to_xml
View
62 app/controllers/users_controller.rb
@@ -32,7 +32,7 @@ def require_invitation
:edit_pro_details, :update_pro_details,
:welcome_photo, :welcome_about, :welcome_invite, :deactivate,
:crop_profile_photo, :upload_profile_photo]
- before_filter :admin_required, :only => [:assume, :destroy, :featured, :toggle_featured, :toggle_moderator]
+ before_filter :admin_required, :only => [:assume, :destroy, :featured, :toggle_featured, :toggle_moderator, :delete_selected]
before_filter :admin_or_current_user_required, :only => [:statistics]
def activate
@@ -68,6 +68,12 @@ def index
@tags = User.tag_counts :limit => 10
setup_metro_areas_for_cloud
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml #{ render :xml => @users }
+ end
+
end
def dashboard
@@ -158,6 +164,9 @@ def update
def destroy
unless @user.admin? || @user.featured_writer?
+ if params[:spam] && AppConfig.akismet_key
+ @user.spam!
+ end
@user.destroy
flash[:notice] = :the_user_was_deleted.l
else
@@ -311,20 +320,6 @@ def welcome_complete
flash[:notice] = :walkthrough_complete.l_with_args(:site => AppConfig.community_name)
redirect_to user_path
end
-
- def forgot_password
- return unless request.post?
-
- @user = User.active.find_by_email(params[:email])
- if @user && @user.reset_password
- UserNotifier.deliver_reset_password(@user)
- @user.save_without_session_maintenance
- redirect_to login_url
- flash[:info] = :your_password_has_been_reset_and_emailed_to_you.l
- else
- flash[:error] = :sorry_we_dont_recognize_that_email_address.l
- end
- end
def forgot_username
return unless request.post?
@@ -357,8 +352,8 @@ def resend_activation
end
def assume
- self.assume_user(User.find(params[:id]))
- redirect_to user_path(current_user)
+ user = User.find(params[:id])
+ redirect_to user_path(self.assume_user(user).record)
end
def return_admin
@@ -407,20 +402,41 @@ def statistics
date = Date.new(params[:date][:year].to_i, params[:date][:month].to_i)
@month = Time.parse(date.to_s)
else
- @month = Date.today
+ @month = Time.now
end
- start_date = @month.beginning_of_month
- end_date = @month.end_of_month + 1.day
+ start_date = @month.utc.beginning_of_month.beginning_of_day
+ end_date = @month.utc.end_of_month.end_of_day
- @posts = @user.posts.find(:all,
- :conditions => ['? <= published_at AND published_at <= ?', start_date, end_date])
+ @posts = @user.posts.find(:all, :conditions => ['? <= published_at AND published_at <= ?', start_date, end_date])
@estimated_payment = @posts.sum do |p|
7
end
- end
+
+ respond_to do |format|
+ format.html
+ format.xml {
+ render :xml => @posts.to_xml(:include => :category)
+ }
+ end
+ end
+ def delete_selected
+ if request.post?
+ if params[:delete]
+ params[:delete].each { |id|
+ user = User.find(id)
+ unless user.admin? || user.featured_writer?
+ user.spam! if params[:spam] && AppConfig.akismet_key
+ user.destroy
+ end
+ }
+ end
+ flash[:notice] = :the_user_was_deleted.l
+ redirect_to admin_users_path
+ end
+ end
protected
def setup_metro_areas_for_cloud
View
6 app/helpers/base_helper.rb
@@ -176,7 +176,7 @@ def page_title
if @page_title
title = @page_title + ' &raquo; ' + app_base + tagline
elsif title == app_base
- title = :showing.l + ' ' + @controller.controller_name.l + ' &raquo; ' + app_base + tagline
+ title = :showing.l + ' ' + @controller.controller_name + ' &raquo; ' + app_base + tagline
end
title
@@ -255,7 +255,7 @@ def show_footer_content?
end
def clippings_link
- "javascript:(function() {d=document, w=window, e=w.getSelection, k=d.getSelection, x=d.selection, s=(e?e():(k)?k():(x?x.createRange().text:0)), e=encodeURIComponent, document.location='#{application_url}new_clipping?uri='+e(document.location)+'&title='+e(document.title)+'&selection='+e(s);} )();"
+ "javascript:(function() {d=document, w=window, e=w.getSelection, k=d.getSelection, x=d.selection, s=(e?e():(k)?k():(x?x.createRange().text:0)), e=encodeURIComponent, document.location='#{home_url}new_clipping?uri='+e(document.location)+'&title='+e(document.title)+'&selection='+e(s);} )();"
end
def paginating_links(paginator, options = {}, html_options = {})
@@ -324,7 +324,7 @@ def time_ago_in_words(from_time, to_time = Time.now, include_seconds = false)
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
distance_in_minutes = (((to_time - from_time).abs)/60).round
-
+
case distance_in_minutes
when 0 then :a_few_seconds_ago.l
when 1..59 then :minutes_ago.l(:count => distance_in_minutes)
View
16 app/helpers/posts_helper.rb
@@ -1,21 +1,5 @@
-require 'hpricot'
-
module PostsHelper
- def post_with_ad_in_content(post)
- string = Ad.display(:post_content, logged_in?)
-
- doc = Hpricot(post.post)
- paragraphs = doc.search("p")
-
- if paragraphs.length > 4
- graph_html = paragraphs[2].inner_html
- paragraphs[2].swap(string + "<p id='jump'>#{graph_html}</p>")
- end
-
- doc.to_html
- end
-
# The ShareThis widget defines a bunch of attributes you can customize.
# Facebook seems to ignore them (it uses title and description meta tags
# instead). MySpace, however, only works if you set these attributes.
View
2 app/helpers/users_helper.rb
@@ -4,7 +4,7 @@ def friends?(user, friend)
end
def random_greeting(user)
- "#{:greetings.l.sort_by {rand}.first} #{user.login}!"
+ "#{:greetings.l.sort_by{rand}.first} #{user.login}!"
end
end
View
24 app/models/comment.rb
@@ -1,5 +1,8 @@
class Comment < ActiveRecord::Base
-
+ include Rakismet::Model
+ rakismet_attrs :author => :author_name, :comment_type => 'comment', :content => :comment, :user_ip => :author_ip
+ attr_protected :akismet_attrs
+
belongs_to :commentable, :polymorphic => true
belongs_to :user
belongs_to :recipient, :class_name => "User", :foreign_key => "recipient_id"
@@ -15,14 +18,15 @@ class Comment < ActiveRecord::Base
validates_presence_of :author_email, :unless => Proc.new{|record| record.user } #require email unless logged in
validates_presence_of :author_ip, :unless => Proc.new{|record| record.user} #log ip unless logged in
validates_format_of :author_url, :with => /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix, :unless => Proc.new{|record| record.user }
-
+ validate :check_spam
+
acts_as_activity :user, :if => Proc.new{|record| record.user } #don't record an activity if there's no user
# named_scopes
named_scope :recent, :order => 'created_at DESC'
def self.find_photo_comments_for(user)
- Comment.find(:all, :conditions => ["recipient_id = ? AND commentable_type = ?", user.id, 'Photo'], :order => 'created_at DESC')
+ Comment.find(:all, :conditions => ["recipient_id = ? AND commentable_type = ?", user.id, 'Photo'], :order => 'created_at DESC', :limit => 10)
end
# Helper class method to lookup all comments assigned
@@ -101,6 +105,7 @@ def notify_previous_anonymous_commenters
end
def send_notifications
+ return if commentable.respond_to?(:send_comment_notifications?) && !commentable.send_comment_notifications?
UserNotifier.deliver_comment_notice(self) if should_notify_recipient?
self.notify_previous_commenters
self.notify_previous_anonymous_commenters if AppConfig.allow_anonymous_commenting
@@ -116,10 +121,15 @@ def unsubscribe_notifications(email)
end
end
- protected
- def whitelist_attributes
- self.comment = white_list(self.comment)
- end
+ def check_spam
+ if AppConfig.akismet_key && self.spam?
+ self.errors.add_to_base(:comment_spam_error.l)
+ end
+ end
+ protected
+ def whitelist_attributes
+ self.comment = white_list(self.comment)
+ end
end
View
2 app/models/homepage_feature.rb
@@ -1,6 +1,6 @@
class HomepageFeature < ActiveRecord::Base
has_attachment prepare_options_for_attachment_fu(AppConfig.feature['attachment_fu_options'])
- attr_accessible :url, :title, :description
+ attr_accessible :url, :title, :description, :position
validates_presence_of :content_type
validates_presence_of :filename
View
34 app/models/message.rb
@@ -3,12 +3,19 @@ class Message < ActiveRecord::Base
attr_accessor :to
attr_accessor :reply_to
+
+ belongs_to :parent, :class_name => "Message", :foreign_key => "parent_id"
+ has_many :children, :class_name => "Message", :foreign_key => "parent_id"
+ has_many :message_threads
+
+ named_scope :parents, :conditions => "parent_id IS NULL"
validates_presence_of :body, :subject
validates_presence_of :recipient, :message => "is invalid"
validate :ensure_not_sending_to_self
after_create :notify_recipient
+ after_create :update_message_threads
def ensure_not_sending_to_self
errors.add_to_base("You may not send a message to yourself.") if self.recipient && self.recipient.eql?(self.sender)
@@ -18,20 +25,31 @@ def notify_recipient
UserNotifier.deliver_message_notification(self)
end
- def self.new_reply(sender, in_reply_to = nil, params = {})
+ def update_message_threads
+ recipients_thread = MessageThread.find_or_create_by_recipient_id_and_parent_message_id(self.recipient_id, (self.parent_id || self.id))
+ recipients_thread.attributes = {:sender => sender, :recipient => recipient, :message => self, :parent_message => (self.parent || self)}
+ recipients_thread.save
+
+ if parent
+ senders_thread = MessageThread.find_or_create_by_recipient_id_and_parent_message_id(self.sender_id, self.parent_id)
+ senders_thread.message = self
+ senders_thread.save
+ end
+ end
+
+ def self.new_reply(sender, message_thread = nil, params = {})
message = new(params[:message])
message.to ||= params[:to] if params[:to]
- if in_reply_to
- return nil if in_reply_to.recipient != sender #can only reply to messages you received
- message.reply_to = in_reply_to
- message.to = in_reply_to.sender.login
- message.subject = "Re: #{in_reply_to.subject}"
- message.body = "\n\n*Original message*\n\n #{in_reply_to.body}"
+ if message_thread
+ message.parent = message_thread.parent_message
+ message.reply_to = message_thread.message
+ message.to = message_thread.sender.login
+ message.subject = message_thread.parent_message.subject
message.sender = sender
end
message
end
-
+
end
View
33 app/models/message_thread.rb
@@ -0,0 +1,33 @@
+class MessageThread < ActiveRecord::Base
+ belongs_to :message
+ belongs_to :parent_message, :class_name => 'Message'
+ belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
+ belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"
+
+ before_destroy :mark_messages_deleted
+
+ def subject
+ parent_message.subject
+ end
+
+ def creator_name
+ parent_message.sender.eql?(recipient) ? 'Me' : parent_message.sender.login
+ end
+
+ def self.for(message, user)
+ find(:first, :conditions => {:parent_message_id => (message.parent_id || message.id), :recipient_id => user.id})
+ end
+
+ def mark_messages_deleted
+ parent_message.mark_deleted(recipient)
+ parent_message.children.each do |child|
+ child.mark_deleted(recipient)
+ end
+ end
+
+ def read?
+ message.recipient.eql?(recipient) ? message.read? : 'read'
+ end
+
+
+end
View
15 app/models/photo.rb
@@ -3,21 +3,6 @@ class Photo < ActiveRecord::Base
belongs_to :album
has_attachment prepare_options_for_attachment_fu(AppConfig.photo['attachment_fu_options'])
- # attr_accessor :cropped_size
- # before_thumbnail_saved do |thumbnail|
- # raise thumbnail.inspect
- # # thumbnail.send(:'attributes=', {:thumbnail_resize_options => cropped_size}, false) if thumbnail.parent.cropped_size
- # if thumbnail.parent.cropped_size[:x1]
- # # img = Magick::Image::read(@photo.public_filename).first
- # thumbnail.crop!(::Magick::CenterGravity, parent.cropped_size[:x1].to_i, parent.cropped_size[:y1].to_i, parent.cropped_size[:width].to_i, parent.cropped_size[:height].to_i, true)
- # raise thumbnail.inspect
- # # size = AppConfig.photo['attachment_fu_options']['thumbnails']['medium']
- # # dimensions = size[1..size.size].split("x")
- # # img.crop_resized!(dimensions[0].to_i, dimensions[1].to_i)
- # # img.write @settings.header_image_file
- # end
- # end
-
acts_as_taggable
View
30 app/models/sb_post.rb
@@ -1,5 +1,7 @@
class SbPost < ActiveRecord::Base
- acts_as_activity :user
+ acts_as_activity :user, :if => Proc.new{|record| record.user } #don't record an activity if there's no user
+ include Rakismet::Model
+ rakismet_attrs :author => :username, :comment_type => 'comment', :content => :body, :user_ip => :author_ip
belongs_to :forum, :counter_cache => true
belongs_to :user, :counter_cache => true
@@ -10,16 +12,24 @@ class SbPost < ActiveRecord::Base
after_create { |r| Topic.update_all(['replied_at = ?, replied_by = ?, last_post_id = ?', r.created_at, r.user_id, r.id], ['id = ?', r.topic_id]) }
after_destroy { |r| t = Topic.find(r.topic_id) ; Topic.update_all(['replied_at = ?, replied_by = ?, last_post_id = ?', t.sb_posts.last.created_at, t.sb_posts.last.user_id, t.sb_posts.last.id], ['id = ?', t.id]) if t.sb_posts.last }
- validates_presence_of :user_id, :body, :topic
- attr_accessible :body
- after_create :monitor_topic
+ validates_presence_of :user_id, :unless => Proc.new{|record| AppConfig.allow_anonymous_forum_posting }
+ validates_presence_of :author_email, :unless => Proc.new{|record| record.user } #require email unless logged in
+ validates_format_of :author_email, :with => /^([^@\s]+)@((?:[-a-z0-9A-Z]+\.)+[a-zA-Z]{2,})$/, :unless => Proc.new{|record| record.user}
+ validates_presence_of :author_ip, :unless => Proc.new{|record| record.user} #log ip unless logged in
+
+ validates_presence_of :body, :topic
+
+ attr_accessible :body, :author_email, :author_ip, :author_name, :author_url
+ after_create :monitor_topic
after_create :notify_monitoring_users
named_scope :with_query_options, :select => 'sb_posts.*, topics.title as topic_title, forums.name as forum_name', :joins => 'inner join topics on sb_posts.topic_id = topics.id inner join forums on topics.forum_id = forums.id', :order => 'sb_posts.created_at desc'
named_scope :recent, :order => 'sb_posts.created_at'
+ validate :check_spam
def monitor_topic
+ return unless user
monitorship = Monitorship.find_or_initialize_by_user_id_and_topic_id(user.id, topic.id)
if monitorship.new_record?
monitorship.update_attribute :active, true
@@ -39,4 +49,16 @@ def to_xml(options = {})
options[:except] << :topic_title << :forum_name
super
end
+
+ def username
+ user ? user.login : (author_name.blank? ? :anonymous.l : author_name)
+ end
+
+ def check_spam
+ if AppConfig.akismet_key && self.spam?
+ self.errors.add_to_base(:comment_spam_error.l)
+ end
+ end
+
+
end
View
32 app/models/user.rb
@@ -1,11 +1,15 @@
require 'digest/sha1'
class User < ActiveRecord::Base
+ include Rakismet::Model
+ rakismet_attrs :author => :login, :comment_type => 'registration', :content => :description, :user_ip => :last_login_ip, :author_email => :email
+ attr_protected :akismet_attrs
+
has_many :albums
MALE = 'M'
FEMALE = 'F'
- attr_protected :admin, :featured, :role_id
+ attr_protected :admin, :featured, :role_id, :akismet_attrs
acts_as_authentic do |c|
c.crypto_provider = CommunityEngineSha1CryptoMethod
@@ -18,10 +22,13 @@ class User < ActiveRecord::Base
c.validates_length_of_email_field_options = { :within => 3..100 }
c.validates_format_of_email_field_options = { :with => /^([^@\s]+)@((?:[-a-z0-9A-Z]+\.)+[a-zA-Z]{2,})$/ }
+ c.perishable_token_valid_for = 2.hours
end
acts_as_taggable
acts_as_commentable
- has_private_messages
+ has_private_messages
+ has_many :message_threads_as_recipient, :class_name => "MessageThread", :foreign_key => "recipient_id"
+
tracks_unlinked_activities [:logged_in, :invited_friends, :updated_profile, :joined_the_site]
#callbacks
@@ -36,10 +43,11 @@ class User < ActiveRecord::Base
#validation
- validates_presence_of :metro_area, :if => Proc.new { |user| user.state }
+ validates_presence_of :metro_area, :if => Proc.new { |user| user.state }
validates_uniqueness_of :login_slug
validates_exclusion_of :login, :in => AppConfig.reserved_logins
validates_date :birthday, :before => 13.years.ago.to_date
+ validate :check_spam
#associations
has_enumerated :role
@@ -66,7 +74,7 @@ class User < ActiveRecord::Base
has_many :monitored_topics, :through => :monitorships, :conditions => ['monitorships.active = ?', true], :order => 'topics.replied_at desc', :source => :topic
belongs_to :avatar, :class_name => "Photo", :foreign_key => "avatar_id"
- belongs_to :metro_area
+ belongs_to :metro_area, :counter_cache => true
belongs_to :state
belongs_to :country
has_many :comments_as_author, :class_name => "Comment", :foreign_key => "user_id", :order => "created_at desc", :dependent => :destroy
@@ -324,6 +332,7 @@ def generate_login_slug
def deliver_activation
UserNotifier.deliver_activation(self) if self.recently_activated?
+ @activated = false
end
def deliver_signup_notification
@@ -427,6 +436,21 @@ def update_last_seen_at
self.sb_last_seen_at = Time.now.utc
end
+ def deliver_password_reset_instructions!
+ reset_perishable_token!
+ UserNotifier.deliver_password_reset_instructions(self)
+ end
+
+ def unread_message_count
+ message_threads_as_recipient.count(:conditions => ["messages.recipient_id = ? AND messages.recipient_deleted = ? AND read_at IS NULL", self.id, false], :include => :message)
+ end
+
+ def check_spam
+ if AppConfig.akismet_key && self.spam?
+ self.errors.add_to_base(:user_spam_error.l)
+ end
+ end
+
## End Instance Methods
View
10 app/models/user_notifier.rb
@@ -61,16 +61,16 @@ def follow_up_comment_notice_anonymous(email, comment)
def new_forum_post_notice(user, post)
setup_email(user)
- @subject += "#{:has_posted_in_a_thread_you_are_monitoring.l(:user => post.user.login)}"
+ @subject += "#{:has_posted_in_a_thread_you_are_monitoring.l(:user => post.username)}"
@body[:url] = "#{forum_topic_url(:forum_id => post.topic.forum, :id => post.topic, :page => post.topic.last_page)}##{post.dom_id}"
@body[:post] = post
- @body[:author] = post.user
+ @body[:author] = post.username
end
def signup_notification(user)
setup_email(user)
@subject += "#{:please_activate_your_new_account.l(:site => AppConfig.community_name)}"
- @body[:url] = "#{application_url}users/activate/#{user.activation_code}"
+ @body[:url] = "#{home_url}users/activate/#{user.activation_code}"
end
def message_notification(message)
@@ -101,9 +101,11 @@ def activation(user)
@body[:url] = home_url
end
- def reset_password(user)
+ def password_reset_instructions(user)
setup_email(user)
@subject += "#{:user_information.l(:site => AppConfig.community_name)}"
+ sent_on Time.now
+ body :edit_password_reset_url => edit_password_reset_url(user.perishable_token)
end
def forgot_username(user)
View
27 app/views/admin/comments.html.haml
@@ -10,12 +10,19 @@
=f.label :commentable_id.l
=f.text_field :commentable_id
+ =f.label :name.l
+ =f.text_field :author_name_or_user_login_like
+
=f.label :email.l
- =f.text_field :author_email_or_user_email_like
+ =f.text_field :author_email_like
+
+ =f.label :comment_web_site_label.l
+ =f.text_field :author_url_like
+ =f.label :body_text.l
+ =f.text_field :comment_like
%p= f.submit :search.l
-
- form_tag delete_selected_comments_path, :id => 'comments' do
@@ -31,7 +38,7 @@
%table{"cellspacing"=>"0", "border"=>"0", "cellpadding"=>"0", "width"=>"100%", :style => "table-layout:fixed;"}
%thead
%tr
- %th{:width => '30px', :colspan => '2'}
+ %th{:width => '60px', :colspan => '3'}
%th=:author.l
%th{:width => "250px"}=:body_text.l
%th=:on_commentable.l
@@ -39,7 +46,11 @@
%tbody
- @comments.each do |comment|
%tr{:id => "comment_#{comment.id}"}
- %td=link_to_remote(image_tag('icons/delete.png', :plugin => 'community_engine'), {:url => comment_path(comment.commentable_type, comment.commentable_id, comment), :method => :delete, 500 => 'alert(\'Sorry, there was a server error\'); return false', :success => visual_effect(:fade, "comment_#{comment.id}"), :confirm => "Are you sure you want to permanently delete this comment"} )
+ %td
+ =link_to_remote(image_tag('icons/delete.png', :plugin => 'community_engine'), {:url => comment_path(comment.commentable_type, comment.commentable_id, comment), :method => :delete, 500 => 'alert(\'Sorry, there was a server error\'); return false', :success => visual_effect(:fade, "comment_#{comment.id}"), :confirm => "Are you sure you want to permanently delete this comment"} )
+ %td
+ -if AppConfig.akismet_key
+ =link_to_remote(image_tag('icons/bad-flag.png', :plugin => 'community_engine'), {:url => comment_path(comment.commentable_type, comment.commentable_id, comment, :spam => true), :method => :delete, 500 => 'alert(\'Sorry, there was a server error\'); return false', :success => visual_effect(:fade, "comment_#{comment.id}"), :confirm => "Are you sure you want to permanently delete this comment and mark it as spam!?"} )
%td= check_box_tag "delete[]", comment.id
%td
.left
@@ -62,7 +73,13 @@
%td{ :colspan => "4" }
-if @comments.any?
%a{:href=>"#", :onclick=>"checkboxes.each(function(e){ e.checked = (e.checked == 0 ? 1 : 0) }); return false;"} Toggle all
- %p= submit_tag :delete_selected.l
+
+ %p
+ -if AppConfig.akismet_key
+ = check_box_tag :spam
+ =:delete_selected_mark_as_spam.l
+ %br
+ = submit_tag :delete_selected.l
-if @comments.page_count > 1
View
2 app/views/admin/messages.html.haml
@@ -1,4 +1,4 @@
.yui-b.sidebar
= render :partial => 'shared/admin_nav'
-= render :partial => "messages/inbox"
+= render :partial => "messages/sent"
View
96 app/views/admin/users.html.haml
@@ -1,41 +1,71 @@
.yui-b.sidebar
= render :partial => 'shared/admin_nav'
+
#yui-main
- -box :class => "yui-b" do
- %h3
- = AppConfig.community_name
- = :members.l
- - form_tag admin_users_url, :class => 'MainForm' do
- %label= :user_login.l
- = text_field_tag 'login', params['login']
- %label= :user_e_mail.l
- = text_field_tag 'email', params['email']
- %p
- %input{:type=>"submit", :value=> :search_users.l }
+ .yui-b
+ -box do
+ %h3
+ = AppConfig.community_name
+ = :members.l
+ - form_tag admin_users_url, :class => 'MainForm' do
+ %label= :user_login.l
+ = text_field_tag 'login', params['login']
+ %label= :user_e_mail.l
+ = text_field_tag 'email', params['email']
+ %p
+ %input{:type=>"submit", :value=> :search_users.l }
- -if @users.page_count > 1
- .pagination= paginating_links @users, :link_to_current_page => true
+ -if @users.page_count > 1
+ .pagination= paginating_links @users, :link_to_current_page => true
- %table{"cellspacing"=>"0", "border"=>"0", "cellpadding"=>"0", "width"=>"100%"}
- %tr
- %th=:login.l
- %th=:e_mail.l
- %th=:status.l
- %th=:actions.l
+ - form_tag delete_selected_users_path, :id => 'users' do
+ %table{"cellspacing"=>"0", "border"=>"0", "cellpadding"=>"0", "width"=>"100%"}
+ %thead
+ %tr
+ %th{:width => '60px', :colspan => '3'}
+ %th=:login.l
+ %th=:e_mail.l
+ %th=:status.l
+ %th=:actions.l
- - @users.each do |user|
- %tr{:id => "user_#{user.id}"}
- %td
- = link_to h(user.login), user_path(user)
- %td
- = h user.email
- %td
- = user.active? ? :active.l : :inactive.l
- %td
- = link_to( :assume_id.l , assume_user_path(user) )
- = (" | " + link_to(:activate.l, :controller => "/admin", :action => "activate_user", :id => user.id)) unless user.active?
- = link_to_remote(image_tag('icons/delete.png', :plugin => 'community_engine'), {:url => user_path(user), :method => :delete, :success => visual_effect(:fade, "user_#{user.id}"), :confirm => "Are you sure you want to permanently delete this user"} )
+ %tbody
+ - @users.each do |user|
+ %tr{:id => "user_#{user.id}"}
+ %td
+ = link_to_remote(image_tag('icons/delete.png', :plugin => 'community_engine'), {:url => user_path(user), :method => :delete, :success => visual_effect(:fade, "user_#{user.id}"), :confirm => "Are you sure you want to permanently delete this user"} )
+ %td
+ -if AppConfig.akismet_key
+ = link_to_remote(image_tag('icons/bad-flag.png', :plugin => 'community_engine'), {:url => user_path(user, :spam => true), :method => :delete, 500 => 'alert(\'Sorry, there was a server error\'); return false', :success => visual_effect(:fade, "user_#{user.id}"), :confirm => "Are you sure you want to permanently delete this user and mark it as spam!?"} )
+
+ %td= check_box_tag "delete[]", user.id
+
+ %td
+ = link_to h(user.login), user_path(user)
+ %td
+ = h user.email
+ %td
+ = user.active? ? :active.l : :inactive.l
+ %td
+ = link_to( :assume_id.l , assume_user_path(user) )
+ = (" | " + link_to(:activate.l, :controller => "/admin", :action => "activate_user", :id => user.id)) unless user.active?
+ %tfoot
+ %tr
+ %td{ :colspan => "4" }
+ -if @users.any?
+ %a{:href=>"#", :onclick=>"checkboxes.each(function(e){ e.checked = (e.checked == 0 ? 1 : 0) }); return false;"} Toggle all
+ %p
+ -if AppConfig.akismet_key
+ = check_box_tag :spam
+ =:delete_selected_mark_as_spam.l
+ %br
+ = submit_tag :delete_selected.l
- -if @users.page_count > 1
- .pagination= paginating_links @users, :link_to_current_page => true
+
+
+ -if @users.page_count > 1
+ .pagination= paginating_links @users, :link_to_current_page => true
+
+%script{:type => 'text/javascript'}
+ var form = $('users');
+ checkboxes = form.getInputs('checkbox');
View
65 app/views/ads/index.html.haml
@@ -1,33 +1,44 @@
.yui-b
= render :partial => 'shared/admin_nav'
+
+ -box do
+ %h3=:search.l
+ - form_for @search, :html => {:class => "MainForm"} do |f|
-#yui-main
- -box :class => "yui-b" do
- %h3= :ads.l
- %table{"width"=>"100%"}
- %tr
- %th=:name.l
- %th=:frequency.l
- %th
- =:published.l
- \?
- %th=:run.l
- %th=:location.l
- %th &nbsp;
+ = f.label :name.l
+ = f.text_field :name_begins_with
+
+ = f.label :location.l
+ = f.text_field :location_contains
+
+ %p= f.submit :search.l
+
- - for ad in @ads
+#yui-main
+ .yui-b
+ -box do
+ %h3= :ads.l
+ %table{"width"=>"100%"}
%tr
- %td= link_to h(ad.name), ad_path(ad)
- %td= h ad.frequency
- %td= h ad.published?
- %td= h ad.time_constrained? ? "#{ad.start_date.to_formatted_s(:short)}-#{ad.end_date.to_formatted_s(:short)}" : "n/a"
- %td= h ad.location
- %td{:colspan => 3}
- = link_to :show.l, ad_path(ad)
- |
- = link_to :edit.l, edit_ad_path(ad)
- |
- = link_to :destroy.l, ad_path(ad), :confirm => :are_you_sure.l, :method => :delete
+ %th=order @search, :by => :name, :as => :name.l
+ %th
+ =order @search, :by => :published, :as => :published.l
+ \?
+ %th
+ =order @search, :by => :location, :as => :location.l
+ %th &nbsp;
+
+ - for ad in @ads
+ %tr
+ %td= link_to h(ad.name), ad_path(ad)
+ %td= h ad.published?
+ %td= h ad.location
+ %td{:colspan => 3}
+ = link_to :edit.l, edit_ad_path(ad)
+ |
+ = link_to :destroy.l, ad_path(ad), :confirm => :are_you_sure.l, :method => :delete
- %br
- = link_to :new_ad.l, new_ad_path
+ .pagination=paginating_links @ads if @ads.page_count > 1
+
+ %br
+ = link_to :new_ad.l, new_ad_path
View
2 app/views/clippings/show.html.haml
@@ -50,7 +50,7 @@
-box :class => 'hfeed comments', :id => 'comments' do
%h3=:clipping_comments.l
%h2=:add_your_comment.l
- %p= render :partial => 'comments/comment_form', :locals => {:commentable => @clipping}
+ = render :partial => 'comments/comment_form', :locals => {:commentable => @clipping}
%a#newest_comment
= render :partial => 'comments/comment', :collection => @clipping.comments
%span#more_comments_links= more_comments_links(@clipping)
View
5 app/views/comments/_comment.html.haml
@@ -11,6 +11,8 @@
= I18n.l(comment.created_at, :format => :short_literal_date)
-if ( comment.can_be_deleted_by(current_user) )
%li.delete=link_to_remote(:delete.l, {:url => comment_path(comment.commentable_type, comment.commentable_id, comment), :method => :delete, 500 => 'alert(\'Sorry, there was a server error\'); return false', :success => visual_effect(:fade, "comment_#{comment.id}"), :confirm => :are_you_sure_you_want_to_permanently_delete_this_comment.l} )
+ -if logged_in? && (current_user.admin? || current_user.moderator?)
+ %li.edit=link_to_remote :edit.l, :url => {:controller => 'comments', :action => 'edit', :id => comment.id}, :method => :get
.entry-content= comment.comment
-else
@@ -28,6 +30,9 @@
= I18n.l(comment.created_at, :format => :short_literal_date)
-if ( comment.can_be_deleted_by(current_user) )
%li.delete=link_to_remote(:delete.l, {:url => comment_path(comment.commentable_type, comment.commentable_id, comment), :method => :delete, 500 => 'alert(\'Sorry, there was a server error\'); return false', :success => visual_effect(:fade, "comment_#{comment.id}"), :confirm => :are_you_sure_you_want_to_permanently_delete_this_comment.l} )
+ -if logged_in? && (current_user.admin? || current_user.moderator?)
+ %li.edit=link_to_remote :edit.l, :url => {:controller => 'comments', :action => 'edit', :id => comment.id}, :method => :get
+
.entry-content= comment.comment
- highlighted ||= nil
View
23 app/views/comments/_comment_form.html.haml
@@ -1,16 +1,15 @@
- if logged_in? || AppConfig.allow_anonymous_commenting
- %script{:type=>"text/javascript"}
- :plain
- function scrollToNewestComment(){
- loc = document.location.toString();
- if (loc.indexOf("#") != -1){
- parts = loc.split('#')
- loc = parts[0] + "#newest_comment"
- } else {
- loc = loc + "#newest_comment";
- }
- document.location.href = loc;
+ :javascript
+ function scrollToNewestComment(){
+ loc = document.location.toString();
+ if (loc.indexOf("#") != -1){
+ parts = loc.split('#')
+ loc = parts[0] + "#newest_comment"
+ } else {
+ loc = loc + "#newest_comment";
}
+ document.location.href = loc;
+ }
.errors
- form_remote_for(:comment, :loading => "$$('div#comments div.errors')[0].innerHTML = ''; $('comment_spinner').show();", :before => "tinyMCE.activeEditor.save();", :url => comments_url(commentable.class.to_s.underscore, commentable.id ), :html => {:id => 'new_comment_form', :class => "MainForm"}) do |f|
@@ -32,6 +31,8 @@
%label
=f.check_box :notify_by_email, :style => 'width: 10px;'
=:notify_me_of_follow_ups_via_email.l
+ -if commentable.respond_to?(:send_comment_notifications?) && !commentable.send_comment_notifications?
+ %em="(#{:comment_notifications_off.l})"
%label{"for"=>"comment[author_url"}
=:comment_web_site_label.l
View
29 app/views/comments/_edit_form.html.haml
@@ -0,0 +1,29 @@
+- form_remote_for(:comment, :loading => "$$('#comment_#{comment.id} .errors')[0].innerHTML = '';", :before => "tinyMCE.activeEditor.save();", :url => {:controller => 'comments', :action => 'update', :id => comment.id}, :method => :put, :html => {:id => "edit_comment_#{comment.id}_form", :class => "MainForm"}) do |f|
+ .errors
+ %label
+ %em=:comment_character_limit.l
+ = text_area :comment, :comment, {:size => "86x5", :class => "rich_text_editor"}
+
+ %label{"for"=>"comment[author_name"}
+ =:name.l
+ %em="(#{:optional.l})"
+ = f.text_field :author_name, :size => 35
+ %label{"for"=>"comment[author_email"}
+ ="#{:email.l}:"
+ %em="(#{:comment_author_email_required.l})"
+ = f.text_field :author_email, :size => 35
+ %br
+ %label
+ =f.check_box :notify_by_email, :style => 'width: 10px;'
+ =:notify_me_of_follow_ups_via_email.l
+ -if comment.commentable.respond_to?(:send_comment_notifications?) && !comment.commentable.send_comment_notifications?
+ %em="(#{:comment_notifications_off.l})"
+
+ %label{"for"=>"comment[author_url"}
+ =:comment_web_site_label.l
+ %em="(#{:optional.l})"
+ = f.text_field :author_url, :size => 35
+
+ %p
+ = submit_tag :add_comment.l
+ = link_to_remote :cancel.l, :url => {:controller => 'comments', :action => 'edit', :id => comment.id, :cancel => true}, :method => :get
View
7 app/views/comments/edit.js.rjs
@@ -0,0 +1,7 @@
+if params[:cancel]
+ page << "tinyMCE.execCommand('mceFocus', false, $$('#edit_comment_#{@comment.id}_form textarea')[0]); tinyMCE.execCommand('mceRemoveControl', false, $$('#edit_comment_#{@comment.id}_form textarea')[0]);"
+ page.replace "comment_#{@comment.id}", :partial => 'comments/comment.html.haml', :locals => {:comment => @comment, :highlighted => true}
+else
+ page.replace_html "comment_#{@comment.id}", :partial => 'comments/edit_form', :locals => {:comment => @comment}
+ page << "tinyMCE.execCommand('mceAddControl', true, $$('#edit_comment_#{@comment.id}_form textarea')[0]);"
+end
View
9 app/views/comments/update.js.rjs
@@ -0,0 +1,9 @@
+if @comment.errors.any?
+ page.select("#comment_#{@comment.id} .errors").invoke('update', @comment.errors.full_messages.to_sentence)
+ page << "new Effect.Highlight($$('#comment_#{@comment.id} .errors')[0]);"
+else
+ page << "tinyMCE.execCommand('mceFocus', false, $$('#edit_comment_#{@comment.id}_form textarea')[0]); tinyMCE.execCommand('mceRemoveControl', false, $$('#edit_comment_#{@comment.id}_form textarea')[0]);"
+ page.replace "comment_#{@comment.id}", :partial => 'comments/comment.html.haml', :locals => {:comment => @comment, :highlighted => true}
+end
+
+
View
2 app/views/events/show.html.haml
@@ -21,7 +21,7 @@
-box :class => 'hfeed comments', :id => 'comments' do
%h3= :event_comments.l
%h2= :add_your_comment.l
- %p= render :partial => 'comments/comment_form', :locals => {:commentable => @event}
+ = render :partial => 'comments/comment_form', :locals => {:commentable => @event}
%a#newest_comment
= render :partial => 'comments/comment', :collection => @comments
%span#more_comments_links= more_comments_links(@event)
View
6 app/views/friendships/index.xml.builder
@@ -1,6 +1,6 @@
xml.instruct!
xml.RelationViewerData do
- xml.Settings :appTitle=>"#{AppConfig.community_name} Friendships Browser", :WWWLinkTargetFrame=>"_blank", :startID=>"#{application_url}#{@user.login_slug}",
+ xml.Settings :appTitle=>"#{AppConfig.community_name} Friendships Browser", :WWWLinkTargetFrame=>"_blank", :startID=>"#{home_url}#{@user.login_slug}",
:defaultRadius=>"170", :maxRadius=>"240", :contextRadius=>"130" do
xml.RelationTypes do
xml.DirectedRelation :color=>"0x999999", :lineSize=>"3"
@@ -14,7 +14,7 @@ xml.RelationViewerData do
@users.each do |user|
imageUrl = (user.avatar_photo_url(:thumb).eql?('icon_missing_thumb.png') ? '/images/icon_missing_thumb.png' : user.avatar_photo_url(:thumb) )
xml.Person :tags => "#{user.tags.collect{|t| t.name }.join(", ")}", :dataURL=>"friendships.xml?id=#{user.id}",
- :id=>"#{application_url}#{user.login_slug}", :name=>"#{user.login}", :imageURL=>imageUrl, :URL=>"#{application_url}#{user.login_slug}" do
+ :id=>"#{home_url}#{user.login_slug}", :name=>"#{user.login}", :imageURL=>imageUrl, :URL=>"#{home_url}#{user.login_slug}" do
xml.cdata!( truncate_words( strip_tags(user.description), 50, '...') )
end
end
@@ -22,7 +22,7 @@ xml.RelationViewerData do
xml.Relations do
@friendships.each do |friendship|
- xml.DirectedRelation :fromID=>"#{application_url}#{friendship.user.login_slug}", :toID=>"#{application_url}#{friendship.friend.login_slug}"
+ xml.DirectedRelation :fromID=>"#{home_url}#{friendship.user.login_slug}", :toID=>"#{home_url}#{friendship.friend.login_slug}"
end
end
View