diff --git a/app/controllers/forum_categories_controller.rb b/app/controllers/forum_categories_controller.rb new file mode 100644 index 000000000..42a0f8d5f --- /dev/null +++ b/app/controllers/forum_categories_controller.rb @@ -0,0 +1,47 @@ +class ForumCategoriesController < ApplicationController + respond_to :html + before_action :admin_only + + def index + @forum_cats = ForumCategory.paginate(params[:page], limit: 50, order: "forum_categories.order ASC, forum_categories.id ASC") + @new_cat = ForumCategory.new + @user_levels = [['Anyone', 0]] + User.level_hash.to_a + end + + def create + @cat = ForumCategory.create(category_params) + if @cat.valid? + ModAction.log(:forum_category_create, {forum_category_id: @cat.id}) + flash[:notice] = "Category created" + else + flash[:notice] = @cat.errors.full_messages.join('; ') + end + redirect_to forum_categories_path + end + + def destroy + @cat = ForumCategory.find(params[:id]) + @cat.destroy + ModAction.log(:forum_category_delete, {forum_category_id: @cat.id}) + respond_with(@cat) + end + + def update + @cat = ForumCategory.find(params[:id]) + @cat.update(category_params) + + if @cat.valid? + ModAction.log(:forum_category_update, {forum_category_id: @cat.id}) + flash[:notice] = "Category updated" + else + flash[:notice] = @cat.errors.full_messages.join('; ') + end + redirect_to forum_categories_path + end + + private + + def category_params + params.require(:forum_category).permit([:name, :description, :can_create, :can_reply, :can_view, :order]) + end +end diff --git a/app/controllers/forum_posts_controller.rb b/app/controllers/forum_posts_controller.rb index fd9a48f35..6188551cd 100644 --- a/app/controllers/forum_posts_controller.rb +++ b/app/controllers/forum_posts_controller.rb @@ -8,11 +8,11 @@ class ForumPostsController < ApplicationController def new if params[:topic_id] @forum_topic = ForumTopic.find(params[:topic_id]) - raise User::PrivilegeError.new unless @forum_topic.visible?(CurrentUser.user) + raise User::PrivilegeError.new unless @forum_topic.visible?(CurrentUser.user) && @forum_topic.can_reply?(CurrentUser.user) end if params[:post_id] quoted_post = ForumPost.find(params[:post_id]) - raise User::PrivilegeError.new unless quoted_post.topic.visible?(CurrentUser.user) + raise User::PrivilegeError.new unless quoted_post.topic.visible?(CurrentUser.user) && quoted_post.topic.can_reply?(CurrentUser.user) end @forum_post = ForumPost.new_reply(params) respond_with(@forum_post) diff --git a/app/helpers/forum_topics_helper.rb b/app/helpers/forum_topics_helper.rb index 2fc818be6..c4cc9186d 100644 --- a/app/helpers/forum_topics_helper.rb +++ b/app/helpers/forum_topics_helper.rb @@ -1,6 +1,6 @@ module ForumTopicsHelper def forum_topic_category_select(object, field) - select(object, field, ForumTopic.reverse_category_mapping.to_a) + select(object, field, ForumCategory.reverse_mapping) end def available_min_user_levels diff --git a/app/models/forum_category.rb b/app/models/forum_category.rb new file mode 100644 index 000000000..722d5ceb9 --- /dev/null +++ b/app/models/forum_category.rb @@ -0,0 +1,22 @@ +class ForumCategory < ApplicationRecord + has_many :forum_topics, -> { order(id: :desc) } + validates_uniqueness_of :name, case_sensitive: false + + after_destroy :reassign_topics + + def reassign_topics + ForumTopic.where(category: @cat.id).update_all(category_id: 0) + end + + def can_create_within?(user = CurrentUser.user) + user.level >= can_create + end + + def self.reverse_mapping + order(:cat_order).all.map { |rec| [rec.name, rec.id] } + end + + def self.ordered_categories + order(:cat_order) + end +end \ No newline at end of file diff --git a/app/models/forum_post.rb b/app/models/forum_post.rb index ead618b2c..a7a301491 100644 --- a/app/models/forum_post.rb +++ b/app/models/forum_post.rb @@ -17,6 +17,7 @@ class ForumPost < ApplicationRecord validate :validate_topic_is_unlocked validate :topic_id_not_invalid validate :topic_is_not_restricted, :on => :create + validate :category_allows_replies, on: :create before_destroy :validate_topic_is_unlocked after_save :delete_topic_if_original_post after_update(:if => ->(rec) {rec.updater_id != rec.creator_id}) do |rec| @@ -158,6 +159,13 @@ class ForumPost < ApplicationRecord end end + def category_allows_replies + if topic && !topic.can_rely?(creator) + errors[:topic] << "does not allow replies" + return false + end + end + def editable_by?(user) (creator_id == user.id || user.is_moderator?) && visible?(user) end diff --git a/app/models/forum_topic.rb b/app/models/forum_topic.rb index 0ebd67aeb..cda86f210 100644 --- a/app/models/forum_topic.rb +++ b/app/models/forum_topic.rb @@ -13,15 +13,17 @@ class ForumTopic < ApplicationRecord belongs_to_creator belongs_to_updater + belongs_to :category, class_name: "ForumCategory", foreign_key: :category_id has_many :posts, -> {order("forum_posts.id asc")}, :class_name => "ForumPost", :foreign_key => "topic_id", :dependent => :destroy has_one :original_post, -> {order("forum_posts.id asc")}, class_name: "ForumPost", foreign_key: "topic_id", inverse_of: :topic has_many :subscriptions, :class_name => "ForumSubscription" before_validation :initialize_is_deleted, :on => :create validates_presence_of :title, :creator_id validates_associated :original_post - validates_inclusion_of :category_id, :in => CATEGORIES.keys + validates_associated :category validates_inclusion_of :min_level, :in => MIN_LEVELS.values validates :title, :length => {:maximum => 255} + validate :category_allows_creation, on: :create accepts_nested_attributes_for :original_post after_update :update_orignal_post after_save(:if => ->(rec) {rec.is_locked? && rec.saved_change_to_is_locked?}) do |rec| @@ -32,21 +34,21 @@ class ForumTopic < ApplicationRecord extend ActiveSupport::Concern module ClassMethods - def categories - CATEGORIES.values - end - - def reverse_category_mapping - @reverse_category_mapping ||= CATEGORIES.invert - end - def for_category_id(cid) where(:category_id => cid) end end def category_name - CATEGORIES[category_id] + return '(Unknown)' unless category + category.name + end + + def category_allows_creation + if category && !category.can_create_within?(creator) + errors[:category] << "doesn't allow new topics" + return false + end end end @@ -56,7 +58,7 @@ class ForumTopic < ApplicationRecord end def permitted - where("min_level <= ?", CurrentUser.level) + where("min_level <= ?", CurrentUser.level).joins(:category).where('forum_categories.can_view <= ?', CurrentUser.level) end def sticky_first @@ -148,7 +150,11 @@ class ForumTopic < ApplicationRecord end def visible?(user) - user.level >= min_level + user.level >= min_level && user.level >= category.can_view + end + + def can_reply?(user = CurrentUser.user) + user.level >= category.can_reply end def create_mod_action_for_delete diff --git a/app/views/forum_categories/index.html.erb b/app/views/forum_categories/index.html.erb new file mode 100644 index 000000000..d187704da --- /dev/null +++ b/app/views/forum_categories/index.html.erb @@ -0,0 +1,44 @@ +
+
+

Note: If any levels are set to "Member", logged-out users will not be able to use that category. Use "Anyone" as + the default.

+
+

New Category

+ <%= simple_form_for(@new_cat) do |f| %> + <%= f.input :name %> + <%= f.input :description %> + <%= f.input :can_create, collection: @user_levels %> + <%= f.input :can_reply, collection: @user_levels %> + <%= f.input :can_view, collection: @user_levels %> + <%= f.button :submit, "Create" %> + <% end %> +
+ + <% @forum_cats.each do |fp| %> +
+

<%= fp.name %>

+ <%= simple_form_for(fp) do |f| %> + <%= f.input :name %> + <%= f.input :description %> + <%= f.input :can_create, collection: @user_levels, selected: fp.can_create %> + <%= f.input :can_reply, collection: @user_levels, selected: fp.can_reply %> + <%= f.input :can_view, collection: @user_levels, selected: fp.can_view %> + <%= f.button :submit, "Update" %> + <% end %> + + <%= link_to "Delete", forum_category_path(id: fp.id), method: :delete, remote: true, 'data-confirm': 'Are you sure you want to delete this category?' %> +
+ <% end %> + +
+ <%= numbered_paginator(@forum_cats) %> +
+
+
+ +<% content_for(:secondary_links) do %> + + <%= subnav_link_to "Forum Topics", forum_posts_path %> + <%= subnav_link_to "Help", help_page_path(id: 'forum') %> + +<% end %> diff --git a/app/views/forum_topics/index.html.erb b/app/views/forum_topics/index.html.erb index f59fd4af6..e9c1d319d 100644 --- a/app/views/forum_topics/index.html.erb +++ b/app/views/forum_topics/index.html.erb @@ -8,7 +8,7 @@ <% if CurrentUser.is_moderator? %> <%= link_to "Mod+", forum_topics_path(:search => {:mod_only => true}) %>, <% end %> - <%= ForumTopic::CATEGORIES.map {|id, name| link_to_unless_current(name, forum_topics_path(:search => {:category_id => id}))}.join(", ").html_safe %> + <%= ForumCategory.ordered_categories.all.map {|rec| link_to_unless_current(rec.name, forum_topics_path(:search => {:category_id => rec.id}))}.join(", ").html_safe %>

<%= render "listing", :forum_topics => @forum_topics %> diff --git a/config/routes.rb b/config/routes.rb index a57c006a3..f31ac2440 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -176,6 +176,7 @@ Rails.application.routes.draw do end resource :visit, :controller => "forum_topic_visits" end + resources :forum_categories resources :help_pages, controller: "help", path: "help" resources :ip_bans resources :upload_whitelists do diff --git a/db/migrate/20190418093745_create_forum_categories.rb b/db/migrate/20190418093745_create_forum_categories.rb new file mode 100644 index 000000000..57058d829 --- /dev/null +++ b/db/migrate/20190418093745_create_forum_categories.rb @@ -0,0 +1,12 @@ +class CreateForumCategories < ActiveRecord::Migration[5.2] + def change + create_table :forum_categories do |t| + t.string :name, null: false + t.text :description + t.integer :cat_order + t.integer :can_view, default: 20, null: false + t.integer :can_create, default: 20, null: false + t.integer :can_reply, default: 20, null: false + end + end +end diff --git a/db/structure.sql b/db/structure.sql index aed94c90d..dabcf1a54 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -878,6 +878,40 @@ CREATE SEQUENCE public.favorites_id_seq ALTER SEQUENCE public.favorites_id_seq OWNED BY public.favorites.id; +-- +-- Name: forum_categories; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.forum_categories ( + id bigint NOT NULL, + name character varying NOT NULL, + description text, + cat_order integer, + can_view integer DEFAULT 20 NOT NULL, + can_create integer DEFAULT 20 NOT NULL, + can_reply integer DEFAULT 20 NOT NULL +); + + +-- +-- Name: forum_categories_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.forum_categories_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: forum_categories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.forum_categories_id_seq OWNED BY public.forum_categories.id; + + -- -- Name: forum_post_votes; Type: TABLE; Schema: public; Owner: - -- @@ -2636,6 +2670,13 @@ ALTER TABLE ONLY public.favorite_groups ALTER COLUMN id SET DEFAULT nextval('pub ALTER TABLE ONLY public.favorites ALTER COLUMN id SET DEFAULT nextval('public.favorites_id_seq'::regclass); +-- +-- Name: forum_categories id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.forum_categories ALTER COLUMN id SET DEFAULT nextval('public.forum_categories_id_seq'::regclass); + + -- -- Name: forum_post_votes id; Type: DEFAULT; Schema: public; Owner: - -- @@ -3099,6 +3140,14 @@ ALTER TABLE ONLY public.favorites ADD CONSTRAINT favorites_pkey PRIMARY KEY (id); +-- +-- Name: forum_categories forum_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.forum_categories + ADD CONSTRAINT forum_categories_pkey PRIMARY KEY (id); + + -- -- Name: forum_post_votes forum_post_votes_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -4760,6 +4809,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20190403174011'), ('20190409195837'), ('20190410022203'), -('20190413055451'); +('20190413055451'), +('20190418093745');