class CommentsController < ApplicationController skip_before_action :store_location, except: [:show, :index, :new] before_action :load_commentable, only: [:index, :new, :create, :edit, :update, :show_comments, :hide_comments, :add_comment_reply, :cancel_comment_reply, :delete_comment, :cancel_comment_delete, :unreviewed, :review_all] before_action :check_user_status, only: [:new, :create, :edit, :update, :destroy] before_action :load_comment, only: [:show, :edit, :update, :delete_comment, :destroy, :cancel_comment_edit, :cancel_comment_delete, :review, :approve, :reject, :freeze, :unfreeze, :hide, :unhide] before_action :check_visibility, only: [:show] before_action :check_if_restricted before_action :check_tag_wrangler_access before_action :check_parent_visible before_action :check_modify_parent, only: [:new, :create, :edit, :update, :add_comment_reply, :cancel_comment_reply, :cancel_comment_edit] before_action :check_pseud_ownership, only: [:create, :update] before_action :check_ownership, only: [:edit, :update, :cancel_comment_edit] before_action :check_permission_to_edit, only: [:edit, :update ] before_action :check_permission_to_delete, only: [:delete_comment, :destroy] before_action :check_guest_comment_admin_setting, only: [:new, :create, :add_comment_reply] before_action :check_parent_comment_permissions, only: [:new, :create, :add_comment_reply] before_action :check_unreviewed, only: [:add_comment_reply] before_action :check_frozen, only: [:new, :create, :add_comment_reply] before_action :check_hidden_by_admin, only: [:new, :create, :add_comment_reply] before_action :check_not_replying_to_spam, only: [:new, :create, :add_comment_reply] before_action :check_guest_replies_preference, only: [:new, :create, :add_comment_reply] before_action :check_permission_to_review, only: [:unreviewed] before_action :check_permission_to_access_single_unreviewed, only: [:show] before_action :check_permission_to_moderate, only: [:approve, :reject] before_action :check_permission_to_modify_frozen_status, only: [:freeze, :unfreeze] before_action :check_permission_to_modify_hidden_status, only: [:hide, :unhide] before_action :admin_logout_required, only: [:new, :create, :add_comment_reply] include BlockHelper before_action :check_blocked, only: [:new, :create, :add_comment_reply, :edit, :update] def check_blocked parent = find_parent if blocked_by?(parent) flash[:comment_error] = t("comments.check_blocked.parent") redirect_to_all_comments(parent, show_comments: true) elsif @comment && blocked_by_comment?(@comment.commentable) # edit and update set @comment to the comment being edited flash[:comment_error] = t("comments.check_blocked.reply") redirect_to_all_comments(parent, show_comments: true) elsif @comment.nil? && blocked_by_comment?(@commentable) # new, create, and add_comment_reply don't set @comment, but do set @commentable flash[:comment_error] = t("comments.check_blocked.reply") redirect_to_all_comments(parent, show_comments: true) end end def check_pseud_ownership return unless params[:comment][:pseud_id] pseud = Pseud.find(params[:comment][:pseud_id]) return if pseud && current_user && current_user.pseuds.include?(pseud) flash[:error] = ts("You can't comment with that pseud.") redirect_to root_path end def load_comment @comment = Comment.find(params[:id]) @check_ownership_of = @comment @check_visibility_of = @comment end def check_parent_visible check_visibility_for(find_parent) end def check_modify_parent parent = find_parent # No one can create or update comments on something hidden by an admin. if parent.respond_to?(:hidden_by_admin) && parent.hidden_by_admin flash[:error] = ts("Sorry, you can't add or edit comments on a hidden work.") redirect_to work_path(parent) end # No one can create or update comments on unrevealed works. if parent.respond_to?(:in_unrevealed_collection) && parent.in_unrevealed_collection flash[:error] = ts("Sorry, you can't add or edit comments on an unrevealed work.") redirect_to work_path(parent) end end def find_parent if @comment.present? @comment.ultimate_parent elsif @commentable.is_a?(Comment) @commentable.ultimate_parent elsif @commentable.present? && @commentable.respond_to?(:work) @commentable.work else @commentable end end # Check to see if the ultimate_parent is a Work, and if so, if it's restricted def check_if_restricted parent = find_parent return unless parent.respond_to?(:restricted) && parent.restricted? && !(logged_in? || logged_in_as_admin?) redirect_to new_user_session_path(restricted_commenting: true) end # Check to see if the ultimate_parent is a Work or AdminPost, and if so, if it allows # comments for the current user. def check_parent_comment_permissions parent = find_parent if parent.is_a?(Work) translation_key = "work" elsif parent.is_a?(AdminPost) translation_key = "admin_post" else return end if parent.disable_all_comments? flash[:error] = t("comments.commentable.permissions.#{translation_key}.disable_all") redirect_to parent elsif parent.disable_anon_comments? && !logged_in? flash[:error] = t("comments.commentable.permissions.#{translation_key}.disable_anon") redirect_to parent end end def check_guest_comment_admin_setting admin_settings = AdminSetting.current return unless admin_settings.guest_comments_off? && guest? flash[:error] = t("comments.commentable.guest_comments_disabled") redirect_back(fallback_location: root_path) end def check_guest_replies_preference return unless guest? && @commentable.respond_to?(:guest_replies_disallowed?) && @commentable.guest_replies_disallowed? flash[:error] = t("comments.check_guest_replies_preference.error") redirect_back(fallback_location: root_path) end def check_unreviewed return unless @commentable.respond_to?(:unreviewed?) && @commentable.unreviewed? flash[:error] = ts("Sorry, you cannot reply to an unapproved comment.") redirect_to logged_in? ? root_path : new_user_session_path end def check_frozen return unless @commentable.respond_to?(:iced?) && @commentable.iced? flash[:error] = t("comments.check_frozen.error") redirect_back(fallback_location: root_path) end def check_hidden_by_admin return unless @commentable.respond_to?(:hidden_by_admin?) && @commentable.hidden_by_admin? flash[:error] = t("comments.check_hidden_by_admin.error") redirect_back(fallback_location: root_path) end def check_not_replying_to_spam return unless @commentable.respond_to?(:approved?) && !@commentable.approved? flash[:error] = t("comments.check_not_replying_to_spam.error") redirect_back(fallback_location: root_path) end def check_permission_to_review parent = find_parent return if logged_in_as_admin? || current_user_owns?(parent) flash[:error] = ts("Sorry, you don't have permission to see those unreviewed comments.") redirect_to logged_in? ? root_path : new_user_session_path end def check_permission_to_access_single_unreviewed return unless @comment.unreviewed? parent = find_parent return if logged_in_as_admin? || current_user_owns?(parent) || current_user_owns?(@comment) flash[:error] = ts("Sorry, that comment is currently in moderation.") redirect_to logged_in? ? root_path : new_user_session_path end def check_permission_to_moderate parent = find_parent unless logged_in_as_admin? || current_user_owns?(parent) flash[:error] = ts("Sorry, you don't have permission to moderate that comment.") redirect_to(logged_in? ? root_path : new_user_session_path) end end def check_tag_wrangler_access if @commentable.is_a?(Tag) || (@comment&.parent&.is_a?(Tag)) logged_in_as_admin? || permit?("tag_wrangler") || access_denied end end # Must be able to delete other people's comments on owned works, not just owned comments! def check_permission_to_delete access_denied(redirect: @comment) unless logged_in_as_admin? || current_user_owns?(@comment) || current_user_owns?(@comment.ultimate_parent) end # Comments cannot be edited after they've been replied to or if they are frozen. def check_permission_to_edit if @comment&.iced? flash[:error] = t("comments.check_permission_to_edit.error.frozen") redirect_back(fallback_location: root_path) elsif !@comment&.count_all_comments&.zero? flash[:error] = ts("Comments with replies cannot be edited") redirect_back(fallback_location: root_path) end end # Comments on works can be frozen or unfrozen by admins with proper # authorization or the work creator. # Comments on tags can be frozen or unfrozen by admins with proper # authorization. # Comments on admin posts can be frozen or unfrozen by any admin. def check_permission_to_modify_frozen_status return if permission_to_modify_frozen_status # i18n-tasks-use t('comments.freeze.permission_denied') # i18n-tasks-use t('comments.unfreeze.permission_denied') flash[:error] = t("comments.#{action_name}.permission_denied") redirect_back(fallback_location: root_path) end def check_permission_to_modify_hidden_status return if policy(@comment).can_hide_comment? # i18n-tasks-use t('comments.hide.permission_denied') # i18n-tasks-use t('comments.unhide.permission_denied') flash[:error] = t("comments.#{action_name}.permission_denied") redirect_back(fallback_location: root_path) end # Get the thing the user is trying to comment on def load_commentable @thread_view = false if params[:comment_id] @thread_view = true if params[:id] @commentable = Comment.find(params[:id]) @thread_root = Comment.find(params[:comment_id]) else @commentable = Comment.find(params[:comment_id]) @thread_root = @commentable end elsif params[:chapter_id] @commentable = Chapter.find(params[:chapter_id]) elsif params[:work_id] @commentable = Work.find(params[:work_id]) elsif params[:admin_post_id] @commentable = AdminPost.find(params[:admin_post_id]) elsif params[:tag_id] @commentable = Tag.find_by_name(params[:tag_id]) @page_subtitle = @commentable.try(:name) end end def index return raise_not_found if @commentable.blank? return unless @commentable.class == Comment # we link to the parent object at the top @commentable = @commentable.ultimate_parent end def unreviewed @comments = @commentable.find_all_comments .unreviewed_only .for_display .page(params[:page]) end # GET /comments/1 # GET /comments/1.xml def show @comments = CommentDecorator.wrap_comments([@comment]) @thread_view = true @thread_root = @comment params[:comment_id] = params[:id] end # GET /comments/new def new if @commentable.nil? flash[:error] = ts("What did you want to comment on?") redirect_back_or_default(root_path) else @comment = Comment.new @controller_name = params[:controller_name] if params[:controller_name] @name = case @commentable.class.name when /Work/ @commentable.title when /Chapter/ @commentable.work.title when /Tag/ @commentable.name when /AdminPost/ @commentable.title when /Comment/ ts("Previous Comment") else @commentable.class.name end end end # GET /comments/1/edit def edit respond_to do |format| format.html format.js end end # POST /comments # POST /comments.xml def create if @commentable.nil? flash[:error] = ts("What did you want to comment on?") redirect_back_or_default(root_path) else @comment = Comment.new(comment_params) @comment.ip_address = request.remote_ip @comment.user_agent = request.env["HTTP_USER_AGENT"]&.to(499) @comment.commentable = Comment.commentable_object(@commentable) @controller_name = params[:controller_name] # First, try saving the comment if @comment.save flash[:comment_notice] = if @comment.unreviewed? # i18n-tasks-use t("comments.create.success.moderated.admin_post") # i18n-tasks-use t("comments.create.success.moderated.work") t("comments.create.success.moderated.#{@comment.ultimate_parent.model_name.i18n_key}") else t("comments.create.success.not_moderated") end respond_to do |format| format.html do if request.referer&.match(/inbox/) redirect_to user_inbox_path(current_user, filters: filter_params[:filters], page: params[:page]) elsif request.referer&.match(/new/) || (@comment.unreviewed? && current_user) # If the referer is the new comment page, go to the comment's page # instead of reloading the full work. # If the comment is unreviewed and commenter is logged in, take # them to the comment's page so they can access the edit and # delete options for the comment, since unreviewed comments don't # appear on the commentable. redirect_to comment_path(@comment) elsif request.referer == root_url # replying on the homepage redirect_to root_path elsif @comment.unreviewed? redirect_to_all_comments(@commentable) else redirect_to_comment(@comment, { view_full_work: (params[:view_full_work] == "true"), page: params[:page] }) end end end else flash[:error] = ts("Couldn't save comment!") render action: "new" end end end # PUT /comments/1 # PUT /comments/1.xml def update updated_comment_params = comment_params.merge(edited_at: Time.current) if @comment.update(updated_comment_params) flash[:comment_notice] = ts('Comment was successfully updated.') respond_to do |format| format.html do redirect_to comment_path(@comment) and return if @comment.unreviewed? redirect_to_comment(@comment) end format.js # updating the comment in place end else render action: "edit" end end # DELETE /comments/1 # DELETE /comments/1.xml def destroy authorize @comment if logged_in_as_admin? parent = @comment.ultimate_parent parent_comment = @comment.reply_comment? ? @comment.commentable : nil unreviewed = @comment.unreviewed? if !@comment.destroy_or_mark_deleted # something went wrong? flash[:comment_error] = ts("We couldn't delete that comment.") redirect_to_comment(@comment) elsif unreviewed # go back to the rest of the unreviewed comments flash[:notice] = ts("Comment deleted.") redirect_back(fallback_location: unreviewed_work_comments_path(@comment.commentable)) elsif parent_comment flash[:comment_notice] = ts("Comment deleted.") redirect_to_comment(parent_comment) else flash[:comment_notice] = ts("Comment deleted.") redirect_to_all_comments(parent, {show_comments: true}) end end def review if logged_in_as_admin? authorize @comment else return unless current_user_owns?(@comment.ultimate_parent) end return unless @comment&.unreviewed? @comment.toggle!(:unreviewed) # mark associated inbox comments as read InboxComment.where(user_id: current_user.id, feedback_comment_id: @comment.id).update_all(read: true) unless logged_in_as_admin? flash[:notice] = ts("Comment approved.") respond_to do |format| format.html do if params[:approved_from] == "inbox" redirect_to user_inbox_path(current_user, page: params[:page], filters: filter_params[:filters]) elsif params[:approved_from] == "home" redirect_to root_path elsif @comment.ultimate_parent.is_a?(AdminPost) redirect_to unreviewed_admin_post_comments_path(@comment.ultimate_parent) else redirect_to unreviewed_work_comments_path(@comment.ultimate_parent) end return end format.js end end def review_all authorize @commentable, policy_class: CommentPolicy if logged_in_as_admin? unless (@commentable && current_user_owns?(@commentable)) || (@commentable && logged_in_as_admin? && @commentable.is_a?(AdminPost)) flash[:error] = ts("What did you want to review comments on?") redirect_back_or_default(root_path) return end @comments = @commentable.find_all_comments.unreviewed_only @comments.each { |c| c.toggle!(:unreviewed) } flash[:notice] = ts("All moderated comments approved.") redirect_to @commentable end def approve authorize @comment @comment.mark_as_ham! redirect_to_all_comments(@comment.ultimate_parent, show_comments: true) end def reject authorize @comment if logged_in_as_admin? @comment.mark_as_spam! redirect_to_all_comments(@comment.ultimate_parent, show_comments: true) end # PUT /comments/1/freeze def freeze # TODO: When AO3-5939 is fixed, we can use # comments = @comment.full_set if @comment.iced? flash[:comment_error] = t(".error") else comments = @comment.set_to_freeze_or_unfreeze Comment.mark_all_frozen!(comments) flash[:comment_notice] = t(".success") end redirect_to_all_comments(@comment.ultimate_parent, show_comments: true) rescue StandardError flash[:comment_error] = t(".error") redirect_to_all_comments(@comment.ultimate_parent, show_comments: true) end # PUT /comments/1/unfreeze def unfreeze # TODO: When AO3-5939 is fixed, we can use # comments = @comment.full_set if @comment.iced? comments = @comment.set_to_freeze_or_unfreeze Comment.mark_all_unfrozen!(comments) flash[:comment_notice] = t(".success") else flash[:comment_error] = t(".error") end redirect_to_all_comments(@comment.ultimate_parent, show_comments: true) rescue StandardError flash[:comment_error] = t(".error") redirect_to_all_comments(@comment.ultimate_parent, show_comments: true) end # PUT /comments/1/hide def hide if !@comment.hidden_by_admin? @comment.mark_hidden! AdminActivity.log_action(current_admin, @comment, action: "hide comment") flash[:comment_notice] = t(".success") else flash[:comment_error] = t(".error") end redirect_to_all_comments(@comment.ultimate_parent, show_comments: true) end # PUT /comments/1/unhide def unhide if @comment.hidden_by_admin? @comment.mark_unhidden! AdminActivity.log_action(current_admin, @comment, action: "unhide comment") flash[:comment_notice] = t(".success") else flash[:comment_error] = t(".error") end redirect_to_all_comments(@comment.ultimate_parent, show_comments: true) end def show_comments respond_to do |format| format.html do # if non-ajax it could mean sudden javascript failure OR being redirected from login # so we're being extra-nice and preserving any intention to comment along with the show comments option options = {show_comments: true} options[:add_comment_reply_id] = params[:add_comment_reply_id] if params[:add_comment_reply_id] options[:view_full_work] = params[:view_full_work] if params[:view_full_work] options[:page] = params[:page] redirect_to_all_comments(@commentable, options) end format.js do @comments = CommentDecorator.for_commentable(@commentable, page: params[:page]) end end end def hide_comments respond_to do |format| format.html do redirect_to_all_comments(@commentable) end format.js end end # If JavaScript is enabled, use add_comment_reply.js to load the reply form # Otherwise, redirect to a comment view with the form already loaded def add_comment_reply @comment = Comment.new respond_to do |format| format.html do options = {show_comments: true} options[:controller] = @commentable.class.to_s.underscore.pluralize options[:anchor] = "comment_#{params[:id]}" options[:page] = params[:page] options[:view_full_work] = params[:view_full_work] if @thread_view options[:id] = @thread_root options[:add_comment_reply_id] = params[:id] redirect_to_comment(@commentable, options) else options[:id] = @commentable.id # work, chapter or other stuff that is not a comment options[:add_comment_reply_id] = params[:id] redirect_to_all_comments(@commentable, options) end end format.js { @commentable = Comment.find(params[:id]) } end end def cancel_comment_reply respond_to do |format| format.html do options = {} options[:show_comments] = params[:show_comments] if params[:show_comments] redirect_to_all_comments(@commentable, options) end format.js { @commentable = Comment.find(params[:id]) } end end def cancel_comment_edit respond_to do |format| format.html { redirect_to_comment(@comment) } format.js end end def delete_comment respond_to do |format| format.html do options = {} options[:show_comments] = params[:show_comments] if params[:show_comments] options[:delete_comment_id] = params[:id] if params[:id] redirect_to_comment(@comment, options) # TO DO: deleting without javascript doesn't work and it never has! end format.js end end def cancel_comment_delete respond_to do |format| format.html do options = {} options[:show_comments] = params[:show_comments] if params[:show_comments] redirect_to_comment(@comment, options) end format.js end end protected # redirect to a particular comment in a thread, going into the thread # if necessary to display it def redirect_to_comment(comment, options = {}) if comment.depth > ArchiveConfig.COMMENT_THREAD_MAX_DEPTH if comment.ultimate_parent.is_a?(Tag) default_options = { controller: :comments, action: :show, id: comment.commentable.id, tag_id: comment.ultimate_parent.to_param, anchor: "comment_#{comment.id}" } else default_options = { controller: comment.commentable.class.to_s.underscore.pluralize, action: :show, id: (comment.commentable.is_a?(Tag) ? comment.commentable.to_param : comment.commentable.id), anchor: "comment_#{comment.id}" } end # display the comment's direct parent (and its associated thread) redirect_to(url_for(default_options.merge(options))) else # need to redirect to the specific chapter; redirect_to_all will then retrieve full work view if applicable redirect_to_all_comments(comment.parent, options.merge({show_comments: true, anchor: "comment_#{comment.id}"})) end end def redirect_to_all_comments(commentable, options = {}) default_options = {anchor: "comments"} options = default_options.merge(options) if commentable.is_a?(Tag) redirect_to comments_path(tag_id: commentable.to_param, add_comment_reply_id: options[:add_comment_reply_id], delete_comment_id: options[:delete_comment_id], page: options[:page], anchor: options[:anchor]) else if commentable.is_a?(Chapter) && (options[:view_full_work] || current_user.try(:preference).try(:view_full_works)) commentable = commentable.work end redirect_to polymorphic_path(commentable, options.slice(:show_comments, :add_comment_reply_id, :delete_comment_id, :view_full_work, :anchor, :page)) end end def permission_to_modify_frozen_status parent = find_parent return true if policy(@comment).can_freeze_comment? return true if parent.is_a?(Work) && current_user_owns?(parent) false end private def comment_params params.require(:comment).permit( :pseud_id, :comment_content, :name, :email, :edited_at ) end def filter_params params.permit! end end