Noosfero/noosfero

View on GitHub
app/models/comment.rb

Summary

Maintainability
B
4 hrs
Test Coverage
class Comment < ApplicationRecord
  include Notifiable

  SEARCHABLE_FIELDS = {
    title: { label: _("Title"), weight: 10 },
    name: { label: _("Name"), weight: 4 },
    body: { label: _("Content"), weight: 2 },
  }

  attr_accessible :body, :author, :name, :email, :title, :reply_of_id, :source, :follow_article

  validates_presence_of :body

  belongs_to :source, counter_cache: true, polymorphic: true, optional: true
  alias :article :source
  alias :article= :source=
  attr_accessor :follow_article

  belongs_to :author, class_name: "Person", foreign_key: "author_id", optional: true
  has_many :children, class_name: "Comment", foreign_key: "reply_of_id", dependent: :destroy
  belongs_to :reply_of, class_name: "Comment", foreign_key: "reply_of_id", optional: true

  scope :without_reply, -> { where "reply_of_id IS NULL" }

  include TimeScopes

  # unauthenticated authors:
  validates_presence_of :name, if: (lambda { |record| !record.email.blank? })
  validates_presence_of :email, if: (lambda { |record| !record.name.blank? })
  validates_format_of :email, with: Noosfero::Constants::EMAIL_FORMAT, if: (lambda { |record| !record.email.blank? })

  # require either a recognized author or an external person
  validates_presence_of :author_id, if: (lambda { |rec| rec.name.blank? && rec.email.blank? })
  validates_each :name do |rec, attribute, value|
    if rec.author_id && (!rec.name.blank? || !rec.email.blank?)
      rec.errors.add(:name, _("{fn} can only be informed for unauthenticated authors").fix_i18n)
    end
  end

  validate :article_archived?

  store_accessor :metadata
  include MetadataScopes

  extend ActsAsHavingSettings::ClassMethods
  acts_as_having_settings

  xss_terminate only: [:body, :title, :name], on: :validation

  acts_as_voteable

  will_notify :new_comment_for_author, push: true
  will_notify :new_comment_for_followers, push: true

  def comment_root
    (reply_of && reply_of.comment_root) || self
  end

  def action_tracker_target
    self.article.profile
  end

  def author_name
    if author
      author.short_name
    else
      author_id ? "" : name
    end
  end

  def author_email
    author ? author.email : email
  end

  def author_link
    author ? author.url : email
  end

  def author_url
    author ? author.url : nil
  end

  # FIXME make this test
  def author_custom_image(size = :icon)
    author ? author.profile_custom_image(size) : nil
  end

  def url
    article.view_url.merge(anchor: anchor)
  end

  def message
    author_id ? _("(removed user)") : _("(unauthenticated user)")
  end

  def removed_user_image
    "/images/icons-app/person-minor.png"
  end

  def anchor
    "comment-#{id}"
  end

  def self.recent(limit = nil)
    self.order("created_at desc, id desc").limit(limit).all
  end

  def notification_emails
    self.article.profile.notification_emails - [self.author_email || self.email]
  end

  after_create :new_follower
  def new_follower
    if source.kind_of?(Article) && !author.nil? && (@follow_article.to_s == "true")
      article.person_followers += [author]
      article.person_followers.to_a.uniq!
      article.save
    end
  end

  after_create :schedule_notification

  def schedule_notification
    Delayed::Job.enqueue CommentHandler.new(self.id, :verify_and_notify)
  end

  delegate :environment, to: :profile

  def environment
    profile && profile.respond_to?(:environment) ? profile.environment : nil
  end

  def profile
    return unless source

    source.kind_of?(Profile) ? source : source.profile
  end

  include Noosfero::Plugin::HotSpot

  include Spammable
  include CacheCounter

  def after_spam!
    SpammerLogger.log(ip_address, self)
    Delayed::Job.enqueue(CommentHandler.new(self.id, :marked_as_spam))
    update_cache_counter(:spam_comments_count, source, 1) if source.kind_of?(Article)
  end

  def after_ham!
    Delayed::Job.enqueue(CommentHandler.new(self.id, :marked_as_ham))
    update_cache_counter(:spam_comments_count, source, -1) if source.kind_of?(Article)
  end

  def verify_and_notify
    check_for_spam
    unless spam?
      send_notifications
    end
  end

  def send_notifications
    if source.kind_of?(Article) && article.notify_comments?
      if !notification_emails.empty?
        notify(:new_comment_for_author, self)
      end
      emails = article.person_followers_email_list - [author_email]
      if !emails.empty?
        notify(:new_comment_for_followers, self, emails)
      end
    end
  end

  after_create do |comment|
    if comment.source.kind_of?(Article)
      comment.article.create_activity if comment.article.activity.nil?
      activity = comment.article.activity
      if activity.present?
        activity.increment!(:comments_count)
        activity.update_attribute(:visible, true)
        activity.touch
      end
    end
  end

  after_destroy do |comment|
    comment.article.activity.decrement!(:comments_count) if comment.source.kind_of?(Article) && comment.article.activity
  end

  def replies
    @replies || children
  end

  def replies=(comments_list)
    @replies = comments_list
  end

  include ApplicationHelper
  def reported_version(options = {})
    comment = self
    lambda { render_to_string(partial: "shared/reported_versions/comment", locals: { comment: comment }) }
  end

  def to_html(option = {})
    body || ""
  end

  def rejected?
    @rejected
  end

  def reject!
    @rejected = true
  end

  def need_moderation?
    article.moderate_comments? && (author.nil? || article.author != author)
  end

  def can_be_destroyed_by?(user)
    return if user.nil?

    user == author || user == profile || user.has_permission?(:moderate_comments, profile)
  end

  # method used by the API
  alias_method :allow_destroy?, :can_be_destroyed_by?

  def can_be_marked_as_spam_by?(user)
    return if user.nil?

    user == profile || user.has_permission?(:moderate_comments, profile)
  end

  def can_be_updated_by?(user)
    user.present? && user == author
  end

  def archived?
    self.source && self.source.is_a?(Article) && self.source.archived?
  end

  def new_comment_for_author_notification
    author = self.article.profile
    if author.respond_to? :push_subscriptions
      new_comment_for_followers_notification.merge(
        recipients: [author]
      )
    end
  end

  protected

    def article_archived?
      errors.add(:article, N_("associated with this comment is archived!")) if archived?
    end

    def new_comment_for_followers_notification
      {
        title: self.article.name,
        body: _("%s published a comment.") % self.author.name,
        recipients: self.article.person_followers
      }
    end
end