Merge pull request #3519 from Roardom/add-forum-categories

(Update) Separate forum categories into their own model
This commit is contained in:
HDVinnie
2024-02-12 22:14:26 -05:00
committed by GitHub
40 changed files with 1023 additions and 581 deletions
@@ -13,7 +13,8 @@
namespace App\Http\Controllers;
use App\Models\Forum;
use App\Models\ForumCategory;
use Illuminate\Http\Request;
/**
* @see \Tests\Feature\Http\Controllers\ForumCategoryControllerTest
@@ -23,24 +24,15 @@ class ForumCategoryController extends Controller
/**
* Show The Forum Category.
*/
public function show(int $id): \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
public function show(Request $request, int $id): \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
{
// Find the topic
$forum = Forum::findOrFail($id);
// Check if this is a category or forum
if ($forum->parent_id !== null) {
return to_route('forums.show', ['id' => $forum->id]);
}
// Check if the user has permission to view the forum
if (!$forum->getPermission()?->read_topic) {
return to_route('forums.index')
->withErrors('You Do Not Have Access To This Category!');
}
return view('forum.category_topic.index', [
'forum' => $forum,
'category' => ForumCategory::query()
->whereHas('forums', fn ($query) => $query->whereRelation('permissions', [
['read_topic', '=', 1],
['group_id', '=', $request->user()->group_id],
]))
->findOrFail($id),
]);
}
}
+11 -21
View File
@@ -14,6 +14,7 @@
namespace App\Http\Controllers;
use App\Models\Forum;
use App\Models\ForumCategory;
use App\Models\Post;
use App\Models\Topic;
use Illuminate\Http\Request;
@@ -29,17 +30,17 @@ class ForumController extends Controller
public function index(Request $request): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('forum.index', [
'categories' => Forum::query()
'categories' => ForumCategory::query()
->with([
'forums' => fn ($query) => $query
->whereRelation('permissions', [['read_topic', '=', 1], ['group_id', '=', $request->user()->group_id]]),
->whereRelation('permissions', [['read_topic', '=', 1], ['group_id', '=', $request->user()->group_id]])
->orderBy('position'),
'forums.latestPoster' => fn ($query) => $query->withTrashed(),
'forums.lastRepliedTopic',
])
->whereNull('parent_id')
->whereRelation('permissions', [['read_topic', '=', 1], ['group_id', '=', $request->user()->group_id]])
->orderBy('position')
->get(),
->get()
->filter(fn ($category) => $category->forums->isNotEmpty()),
'num_posts' => Post::count(),
'num_forums' => Forum::count(),
'num_topics' => Topic::count(),
@@ -49,24 +50,13 @@ class ForumController extends Controller
/**
* Show Forums And Topics Inside.
*/
public function show(int $id): \Illuminate\Contracts\View\Factory|\Illuminate\View\View|\Illuminate\Http\RedirectResponse
public function show(Request $request, int $id): \Illuminate\Contracts\View\Factory|\Illuminate\View\View|\Illuminate\Http\RedirectResponse
{
// Find the topic
$forum = Forum::findOrFail($id);
// Check if this is a category or forum
if ($forum->parent_id === null) {
return to_route('forums.categories.show', ['id' => $forum->id]);
}
// Check if the user has permission to view the forum
if (!$forum->getPermission()?->read_topic) {
return to_route('forums.index')
->withErrors('You Do Not Have Access To This Forum!');
}
return view('forum.forum_topic.index', [
'forum' => $forum,
'forum' => Forum::query()
->with('category')
->whereRelation('permissions', [['read_topic', '=', 1], ['group_id', '=', $request->user()->group_id]])
->findOrFail($id),
]);
}
}
+1 -1
View File
@@ -103,7 +103,7 @@ class PostController extends Controller
$realUrl = sprintf('/forums/topics/%s/posts/%s', $topic->id, $post->id);
$profileUrl = sprintf('%s/users/%s', $appUrl, $user->username);
if (config('other.staff-forum-notify') && ($forum->id == config('other.staff-forum-id') || $forum->parent_id == config('other.staff-forum-id'))) {
if (config('other.staff-forum-notify') && ($forum->id == config('other.staff-forum-id') || $forum->forum_category_id == config('other.staff-forum-id'))) {
$topic->notifyStaffers($user, $topic, $post);
} else {
$this->chatRepository->systemMessage(sprintf('[url=%s]%s[/url] has left a reply on topic [url=%s]%s[/url]', $profileUrl, $user->username, $postUrl, $topic->name));
@@ -0,0 +1,68 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Controllers\Staff;
use App\Http\Controllers\Controller;
use App\Http\Requests\Staff\StoreForumCategoryRequest;
use App\Http\Requests\Staff\UpdateForumCategoryRequest;
use App\Models\ForumCategory;
class ForumCategoryController extends Controller
{
public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('Staff.forum-category.index', [
'categories' => ForumCategory::query()
->with(['forums' => fn ($query) => $query->orderBy('position')])
->orderBy('position')
->get(),
]);
}
public function create(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('Staff.forum-category.create');
}
public function store(StoreForumCategoryRequest $request): \Illuminate\Http\RedirectResponse
{
ForumCategory::create($request->validated());
return to_route('staff.forum_categories.index')
->withSuccess('Forum has been created successfully');
}
public function edit(ForumCategory $forumCategory): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('Staff.forum-category.edit', [
'forumCategory' => $forumCategory,
]);
}
public function update(UpdateForumCategoryRequest $request, ForumCategory $forumCategory): \Illuminate\Http\RedirectResponse
{
$forumCategory->update($request->validated());
return to_route('staff.forum_categories.index')
->withSuccess('Forum has been edited successfully');
}
public function destroy(ForumCategory $forumCategory): \Illuminate\Http\RedirectResponse
{
$forumCategory->delete();
return to_route('staff.forum_categories.index')
->withSuccess('Forum has been deleted successfully');
}
}
+20 -102
View File
@@ -17,37 +17,26 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Staff\StoreForumRequest;
use App\Http\Requests\Staff\UpdateForumRequest;
use App\Models\Forum;
use App\Models\ForumCategory;
use App\Models\Group;
use App\Models\Permission;
use Illuminate\Support\Str;
use Exception;
use Illuminate\Http\Request;
/**
* @see \Tests\Todo\Feature\Http\Controllers\Staff\ForumControllerTest
*/
class ForumController extends Controller
{
/**
* Display All Forums.
*/
public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('Staff.forum.index', [
'categories' => Forum::orderBy('position')
->whereNull('parent_id')
->with(['forums' => fn ($query) => $query->orderBy('position')])
->get(),
]);
}
/**
* Show Forum Create Form.
*/
public function create(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
public function create(Request $request): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('Staff.forum.create', [
'categories' => Forum::whereNull('parent_id')->get(),
'groups' => Group::orderBy('position')->get(),
'forumCategoryId' => $request->integer('forumCategoryId'),
'categories' => ForumCategory::orderBy('position')->get(),
'groups' => Group::orderBy('position')->get(),
]);
}
@@ -56,45 +45,14 @@ class ForumController extends Controller
*/
public function store(StoreForumRequest $request): \Illuminate\Http\RedirectResponse
{
$groups = Group::all();
$forum = Forum::create($request->safe(['name', 'position', 'description', 'slug', 'forum_category_id']));
$forum = Forum::create(
['slug' => Str::slug($request->title)]
+ $request->safe()->only(
[
'name',
'position',
'description',
'parent_id'
]
)
Permission::upsert(
array_map(fn ($item) => ['forum_id' => $forum->id] + $item, $request->validated('permissions')),
['forum_id', 'group_id']
);
// Permissions
foreach ($groups as $group) {
$perm = Permission::where('forum_id', '=', $forum->id)->where('group_id', '=', $group->id)->first();
if ($perm == null) {
$perm = new Permission();
}
$perm->forum_id = $forum->id;
$perm->group_id = $group->id;
if (\array_key_exists($group->id, $request->input('permissions'))) {
$perm->read_topic = isset($request->input('permissions')[$group->id]['read_topic']);
$perm->reply_topic = isset($request->input('permissions')[$group->id]['reply_topic']);
$perm->start_topic = isset($request->input('permissions')[$group->id]['start_topic']);
} else {
$perm->read_topic = false;
$perm->reply_topic = false;
$perm->start_topic = false;
}
$perm->save();
}
return to_route('staff.forums.index')
return to_route('staff.forum_categories.index')
->withSuccess('Forum has been created successfully');
}
@@ -104,9 +62,9 @@ class ForumController extends Controller
public function edit(Forum $forum): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('Staff.forum.edit', [
'categories' => Forum::whereNull('parent_id')->get(),
'categories' => ForumCategory::orderBy('position')->get(),
'groups' => Group::orderBy('position')->get(),
'forum' => $forum->load('permissions'),
'forum' => $forum->load(['permissions', 'category']),
]);
}
@@ -115,37 +73,14 @@ class ForumController extends Controller
*/
public function update(UpdateForumRequest $request, Forum $forum): \Illuminate\Http\RedirectResponse
{
$groups = Group::all();
$forum->update($request->safe(['name', 'position', 'description', 'slug', 'forum_category_id']));
$forum->update(
[
'slug' => Str::slug($request->title),
'parent_id' => $request->forum_type === 'category' ? null : $request->parent_id,
]
+ $request->safe()->only(['name', 'position', 'description'])
Permission::upsert(
array_map(fn ($item) => ['forum_id' => $forum->id] + $item, $request->validated('permissions')),
['forum_id', 'group_id']
);
// Permissions
foreach ($groups as $group) {
$permission = Permission::whereBelongsTo($forum)->whereBelongsTo($group)->firstOrNew([
'forum_id' => $forum->id,
'group_id' => $group->id,
]);
if (\array_key_exists($group->id, $request->input('permissions'))) {
$permission->read_topic = isset($request->input('permissions')[$group->id]['read_topic']);
$permission->reply_topic = isset($request->input('permissions')[$group->id]['reply_topic']);
$permission->start_topic = isset($request->input('permissions')[$group->id]['start_topic']);
} else {
$permission->read_topic = false;
$permission->reply_topic = false;
$permission->start_topic = false;
}
$permission->save();
}
return to_route('staff.forums.index')
return to_route('staff.forum_categories.index')
->withSuccess('Forum has been edited successfully');
}
@@ -156,26 +91,9 @@ class ForumController extends Controller
*/
public function destroy(Forum $forum): \Illuminate\Http\RedirectResponse
{
$forum->permissions()->delete();
$forum->delete();
if ($forum->parent_id === null) {
$category = $forum;
foreach ($category->forums as $forum) {
$forum->permissions()->delete();
$forum->posts()->delete();
$forum->topics()->delete();
$forum->delete();
}
$category->delete();
} else {
$forum->posts()->delete();
$forum->topics()->delete();
$forum->delete();
}
return to_route('staff.forums.index')
return to_route('staff.forum_categories.index')
->withSuccess('Forum has been deleted successfully');
}
}
+13 -14
View File
@@ -60,15 +60,13 @@ class TopicController extends Controller
{
$user = $request->user();
$topic = Topic::with('user')
$topic = Topic::with('user', 'forum.category')
->whereRelation('forumPermissions', [
['read_topic', '=', 1],
['group_id', '=', $user->group_id],
])
->findOrFail($id);
$forum = $topic->forum->load('category');
$subscription = Subscription::where('user_id', '=', $user->id)->where('topic_id', '=', $id)->first();
$topic->views++;
@@ -76,7 +74,6 @@ class TopicController extends Controller
return view('forum.topic.show', [
'topic' => $topic,
'forum' => $forum,
'subscription' => $subscription,
]);
}
@@ -153,7 +150,7 @@ class TopicController extends Controller
$topicUrl = sprintf('%s/forums/topics/%s', $appUrl, $topic->id);
$profileUrl = sprintf('%s/users/%s', $appUrl, $user->username);
if (config('other.staff-forum-notify') && ($forum->id == config('other.staff-forum-id') || $forum->parent_id == config('other.staff-forum-id'))) {
if (config('other.staff-forum-notify') && ($forum->id == config('other.staff-forum-id') || $forum->forum_category_id == config('other.staff-forum-id'))) {
$forum->notifyStaffers($user, $topic);
} else {
$this->chatRepository->systemMessage(sprintf('[url=%s]%s[/url] has created a new topic [url=%s]%s[/url]', $profileUrl, $user->username, $topicUrl, $topic->name));
@@ -195,16 +192,14 @@ class TopicController extends Controller
abort_unless($user->group->is_modo || $user->id === $topic->first_post_user_id, 403);
$categories = Forum::whereRelation('permissions', [
['start_topic', '=', 1],
])
->whereNull('parent_id')
->with([
'forums' => fn ($query) => $query->whereRelation('permissions', [
['start_topic', '=', 1],
])
$categories = Forum::with('category:id,name')
->whereRelation('permissions', [
['read_topic', '=', 1],
['start_topic', '=', 1],
['group_id', '=', $request->user()->group_id],
])
->get();
->get()
->groupBy('category.name');
return view('forum.topic.edit', [
'topic' => $topic,
@@ -225,6 +220,10 @@ class TopicController extends Controller
]);
$topic = Topic::query()
->whereRelation('forumPermissions', [
['read_topic', '=', 1],
['group_id', '=', $user->group_id],
])
->when(!$user->group->is_modo, fn ($query) => $query->where('state', '=', 'open'))
->findOrFail($id);
@@ -14,6 +14,7 @@
namespace App\Http\Livewire;
use App\Models\Forum;
use App\Models\ForumCategory;
use App\Models\Topic;
use Livewire\Component;
use Livewire\WithPagination;
@@ -28,7 +29,7 @@ class ForumCategoryTopicSearch extends Component
public string $label = '';
public string $state = '';
public string $subscribed = '';
public Forum $category;
public ForumCategory $category;
/**
* @var array<mixed>
@@ -42,7 +43,7 @@ class ForumCategoryTopicSearch extends Component
'subscribed' => ['except' => ''],
];
final public function mount(Forum $category): void
final public function mount(ForumCategory $category): void
{
$this->category = $category;
}
@@ -64,8 +65,8 @@ class ForumCategoryTopicSearch extends Component
{
return Topic::query()
->select('topics.*')
->with('user', 'user.group', 'latestPoster')
->whereIn('forum_id', Forum::where('parent_id', '=', $this->category->id)->select('id'))
->with('user', 'user.group', 'latestPoster', 'forum')
->whereIn('forum_id', Forum::where('forum_category_id', '=', $this->category->id)->select('id'))
->whereRelation('forumPermissions', [['read_topic', '=', 1], ['group_id', '=', auth()->user()->group_id]])
->when($this->search !== '', fn ($query) => $query->where('name', 'LIKE', '%'.$this->search.'%'))
->when($this->label !== '', fn ($query) => $query->where($this->label, '=', 1))
-1
View File
@@ -28,7 +28,6 @@ class SubscribedForum extends Component
{
return Forum::query()
->with('latestPoster', 'lastRepliedTopic')
->whereNotNull('parent_id')
->whereRelation('subscribedUsers', 'users.id', '=', auth()->id())
->whereRelation('permissions', [['read_topic', '=', 1], ['group_id', '=', auth()->user()->group_id]])
->orderBy('position')
+8 -13
View File
@@ -13,7 +13,7 @@
namespace App\Http\Livewire;
use App\Models\Forum;
use App\Models\ForumCategory;
use App\Models\Topic;
use Livewire\Component;
use Livewire\WithPagination;
@@ -28,7 +28,7 @@ class TopicSearch extends Component
public string $label = '';
public string $state = '';
public string $subscribed = '';
public String $forumId = '';
public string $forumId = '';
/**
* @var array<mixed>
@@ -54,18 +54,17 @@ class TopicSearch extends Component
}
/**
* @return \Illuminate\Support\Collection<int, Forum>
* @return \Illuminate\Database\Eloquent\Collection<int, ForumCategory>
*/
final public function getForumCategoriesProperty(): \Illuminate\Support\Collection
final public function getForumCategoriesProperty(): \Illuminate\Database\Eloquent\Collection
{
return Forum::query()
return ForumCategory::query()
->with(['forums' => fn ($query) => $query
->whereRelation('permissions', [['read_topic', '=', 1], ['group_id', '=', auth()->user()->group_id]])
])
->whereNull('parent_id')
->whereRelation('permissions', [['read_topic', '=', 1], ['group_id', '=', auth()->user()->group_id]])
->orderBy('position')
->get();
->get()
->filter(fn ($category) => $category->forums->isNotEmpty());
}
/**
@@ -90,11 +89,7 @@ class TopicSearch extends Component
fn ($query) => $query
->whereDoesntHave('subscribedUsers', fn ($query) => $query->where('users.id', '=', auth()->id()))
)
->when($this->forumId !== '', fn ($query) => $query->where(
fn ($query) => $query
->where('forum_id', '=', $this->forumId)
->orWhereIn('forum_id', Forum::where('parent_id', '=', $this->forumId)->select('id'))
))
->when($this->forumId !== '', fn ($query) => $query->where('forum_id', '=', $this->forumId))
->orderBy($this->sortField, $this->sortDirection)
->paginate(25);
}
@@ -0,0 +1,53 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Requests\Staff;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
class StoreForumCategoryRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\Rule|array<\Illuminate\Contracts\Validation\Rule|string>|string>
*/
public function rules(): array
{
return [
'name' => [
'required',
],
'slug' => [
'required',
],
'position' => [
'required',
],
'description' => [
'required',
],
];
}
/**
* Prepare the data for validation.
*/
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->name),
]);
}
}
+27 -9
View File
@@ -14,6 +14,8 @@
namespace App\Http\Requests\Staff;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class StoreForumRequest extends FormRequest
{
@@ -39,34 +41,50 @@ class StoreForumRequest extends FormRequest
'position' => [
'required',
],
'slug' => [
'required',
],
'description' => [
'required',
],
'parent_id' => [
'sometimes',
'nullable',
'integer',
'forum_category_id' => [
'required',
'exists:forum_categories,id',
],
'permissions' => [
'sometimes',
'required',
'array',
],
'permissions.*' => [
'sometimes',
'required',
'array:group_id,read_topic,reply_topic,start_topic',
],
'permissions.*.group_id' => [
'required',
'exists:groups,id',
],
'permissions.*.read_topic' => [
'sometimes',
'required',
'boolean',
],
'permissions.*.reply_topic' => [
'sometimes',
'required',
'boolean',
],
'permissions.*.start_topic' => [
'sometimes',
'required',
'boolean',
],
];
}
/**
* Prepare the data for validation.
*/
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->name),
]);
}
}
@@ -0,0 +1,53 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Requests\Staff;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
class UpdateForumCategoryRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\Rule|array<\Illuminate\Contracts\Validation\Rule|string>|string>
*/
public function rules(): array
{
return [
'name' => [
'required',
],
'slug' => [
'required',
],
'position' => [
'required',
],
'description' => [
'required',
],
];
}
/**
* Prepare the data for validation.
*/
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->name),
]);
}
}
+26 -7
View File
@@ -14,6 +14,7 @@
namespace App\Http\Requests\Staff;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
class UpdateForumRequest extends FormRequest
{
@@ -39,32 +40,50 @@ class UpdateForumRequest extends FormRequest
'position' => [
'required',
],
'slug' => [
'required',
],
'description' => [
'required',
],
'parent_id' => [
'sometimes',
'nullable',
'integer',
'forum_category_id' => [
'required',
'exists:forum_categories,id',
],
'permissions' => [
'required',
'array',
],
'permissions.*' => [
'required',
'array:group_id,read_topic,reply_topic,start_topic',
],
'permissions.*.group_id' => [
'required',
'exists:groups,id',
],
'permissions.*.read_topic' => [
'required',
'boolean',
],
'permissions.*.reply_topic' => [
'required',
'boolean',
],
'permissions.*.start_topic' => [
'required',
'boolean',
],
'forum_type' => [
'in:category,forum',
],
];
}
/**
* Prepare the data for validation.
*/
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->name),
]);
}
}
+9 -30
View File
@@ -32,7 +32,7 @@ use Illuminate\Database\Eloquent\Model;
* @property string|null $name
* @property string|null $slug
* @property string|null $description
* @property int|null $parent_id
* @property int $forum_category_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
*/
@@ -58,36 +58,14 @@ class Forum extends Model
return $this->hasMany(Topic::class);
}
/**
* Has Many Sub Topics.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany<Topic>
*/
public function sub_topics(): \Illuminate\Database\Eloquent\Relations\HasMany
{
$children = $this->forums->pluck('id')->toArray();
return $this->hasMany(Topic::class)->orWhereIn('topics.forum_id', $children);
}
/**
* Has Many Sub Forums.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany<self>
*/
public function forums(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(self::class, 'parent_id', 'id');
}
/**
* Returns The Category In Which The Forum Is Located.
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne<self>
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<ForumCategory, self>
*/
public function category(): \Illuminate\Database\Eloquent\Relations\HasOne
public function category(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->hasOne(self::class, 'id', 'parent_id');
return $this->belongsTo(ForumCategory::class, 'forum_category_id');
}
/**
@@ -117,7 +95,7 @@ class Forum extends Model
*/
public function lastRepliedTopic(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Topic::class, 'last_post_topic_id');
return $this->belongsTo(Topic::class, 'last_topic_id');
}
/**
@@ -200,8 +178,9 @@ class Forum extends Model
*/
public function getPermission(): ?Permission
{
$group = auth()->check() ? auth()->user()->group : Group::where('slug', 'guest')->first();
return $group->permissions->where('forum_id', $this->id)->first();
return Permission::query()
->where('group_id', '=', auth()->user()->group_id)
->where('forum_id', '=', $this->id)
->first();
}
}
+62
View File
@@ -0,0 +1,62 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Models;
use App\Traits\Auditable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* App\Models\Forum.
*
* @property int $id
* @property int|null $position
* @property string $name
* @property string $slug
* @property string $description
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
*/
class ForumCategory extends Model
{
use Auditable;
use HasFactory;
/**
* The attributes that aren't mass assignable.
*
* @var string[]
*/
protected $guarded = ['id', 'created_at'];
/**
* Has Many Sub Topics.
*
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough<Topic>
*/
public function topics(): \Illuminate\Database\Eloquent\Relations\HasManyThrough
{
return $this->hasManyThrough(Topic::class, Forum::class);
}
/**
* Has Many Sub Forums.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany<Forum>
*/
public function forums(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Forum::class);
}
}
+20 -3
View File
@@ -22,9 +22,26 @@ return [
*/
'global_discards' => [
'password', 'passkey', 'rsskey', 'ip', 'remember_token',
'views', 'num_post', 'read', 'nfo',
'last_post_created_at', 'last_action', 'created_at', 'updated_at', 'deleted_at',
'password',
'passkey',
'rsskey',
'ip',
'remember_token',
'views',
'num_post',
'num_topic',
'read',
'nfo',
'last_post_id',
'last_topic_id',
'first_post_user_id',
'last_post_id',
'last_post_user_id',
'last_post_created_at',
'last_action',
'created_at',
'updated_at',
'deleted_at',
],
/*
@@ -0,0 +1,39 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace Database\Factories;
use App\Models\ForumCategory;
use Illuminate\Database\Eloquent\Factories\Factory;
/** @extends Factory<ForumCategory> */
class ForumCategoryFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*/
protected $model = ForumCategory::class;
/**
* Define the model's default state.
*/
public function definition(): array
{
return [
'position' => $this->faker->numberBetween(0, 65535),
'name' => $this->faker->name(),
'slug' => $this->faker->slug(),
'description' => $this->faker->text(),
];
}
}
+2 -1
View File
@@ -13,6 +13,7 @@
namespace Database\Factories;
use App\Models\ForumCategory;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\Forum;
@@ -40,7 +41,7 @@ class ForumFactory extends Factory
'name' => $this->faker->name(),
'slug' => $this->faker->slug(),
'description' => $this->faker->text(),
'parent_id' => $this->faker->randomDigitNotNull(),
'forum_category_id' => ForumCategory::factory(),
];
}
}
@@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
public function up(): void
{
Schema::create('forum_categories', function (Blueprint $table): void {
$table->smallIncrements('id');
$table->unsignedSmallInteger('position');
$table->string('slug');
$table->string('name');
$table->string('description');
$table->timestamps();
});
DB::table('forum_categories')->insertUsing(
[
'id',
'position',
'slug',
'name',
'description',
'created_at',
'updated_at',
],
DB::table('forums')->select([
'id',
DB::raw("COALESCE(position, 0) as position"),
DB::raw("COALESCE(slug, '') as slug"),
DB::raw("COALESCE(name, '') as name"),
DB::raw("COALESCE(description, '') as description"),
'created_at',
'updated_at',
])
->whereNull('parent_id')
);
Schema::table('forums', function (Blueprint $table): void {
$table->dropForeign(['parent_id']);
$table->renameColumn('parent_id', 'forum_category_id');
});
DB::table('forums')
->whereNull('forum_category_id')
->delete();
Schema::table('forums', function (Blueprint $table): void {
$table->unsignedSmallInteger('forum_category_id')->nullable(false)->change();
$table->foreign('forum_category_id')->references('id')->on('forum_categories')->cascadeOnUpdate()->cascadeOnDelete();
});
// Remove existing duplicates
DB::table('permissions as p1')
->join('permissions as p2', function ($join): void {
$join->on('p1.id', '<', 'p2.id')
->whereColumn('p1.group_id', '=', 'p2.group_id')
->whereColumn('p1.forum_id', '=', 'p2.forum_id');
})
->delete();
Schema::table('permissions', function (Blueprint $table): void {
$table->unique(['group_id', 'forum_id']);
});
}
};
+13 -16
View File
@@ -14,29 +14,26 @@
namespace Database\Seeders;
use App\Models\Forum;
use App\Models\ForumCategory;
use Illuminate\Database\Seeder;
class ForumsTableSeeder extends Seeder
{
public function run(): void
{
Forum::upsert([
ForumCategory::upsert([
[
'id' => 1,
'position' => 1,
'num_topic' => null,
'num_post' => null,
'last_topic_id' => null,
'last_post_id' => null,
'last_post_user_id' => null,
'last_post_created_at' => null,
'name' => 'UNIT3D Forums',
'slug' => 'unit3d-forums',
'description' => 'UNIT3D Forums',
'parent_id' => null,
'created_at' => '2017-01-03 18:29:21',
'updated_at' => '2017-01-03 18:29:21',
'id' => 1,
'position' => 1,
'name' => 'UNIT3D Forums',
'slug' => 'unit3d-forums',
'description' => 'UNIT3D Forums',
'created_at' => '2017-01-03 18:29:21',
'updated_at' => '2017-01-03 18:29:21',
],
], ['id']);
Forum::upsert([
[
'id' => 2,
'position' => 2,
@@ -49,7 +46,7 @@ class ForumsTableSeeder extends Seeder
'name' => 'Welcome',
'slug' => 'welcome',
'description' => 'Introduce Yourself Here!',
'parent_id' => 1,
'forum_category_id' => 1,
'created_at' => '2017-04-01 20:16:06',
'updated_at' => '2017-12-27 18:19:07',
],
-30
View File
@@ -800,36 +800,6 @@ parameters:
count: 1
path: app/Http/Controllers/Staff/FlushController.php
-
message: "#^Property App\\\\Models\\\\Permission\\:\\:\\$read_topic \\(int\\) does not accept bool\\.$#"
count: 2
path: app/Http/Controllers/Staff/ForumController.php
-
message: "#^Property App\\\\Models\\\\Permission\\:\\:\\$read_topic \\(int\\) does not accept false\\.$#"
count: 2
path: app/Http/Controllers/Staff/ForumController.php
-
message: "#^Property App\\\\Models\\\\Permission\\:\\:\\$reply_topic \\(int\\) does not accept bool\\.$#"
count: 2
path: app/Http/Controllers/Staff/ForumController.php
-
message: "#^Property App\\\\Models\\\\Permission\\:\\:\\$reply_topic \\(int\\) does not accept false\\.$#"
count: 2
path: app/Http/Controllers/Staff/ForumController.php
-
message: "#^Property App\\\\Models\\\\Permission\\:\\:\\$start_topic \\(int\\) does not accept bool\\.$#"
count: 2
path: app/Http/Controllers/Staff/ForumController.php
-
message: "#^Property App\\\\Models\\\\Permission\\:\\:\\$start_topic \\(int\\) does not accept false\\.$#"
count: 2
path: app/Http/Controllers/Staff/ForumController.php
-
message: "#^Parameter \\#1 \\$boolean of function abort_if expects bool, int given\\.$#"
count: 3
@@ -151,7 +151,7 @@
<p class="form__group form__group--horizontal">
<a
class="form__button form__button--text"
href="{{ route('staff.forums.index') }}"
href="{{ route('staff.forum_categories.index') }}"
>
<i class="fab fa-wpforms"></i>
{{ __('staff.forums') }}
@@ -0,0 +1,70 @@
@extends('layout.default')
@section('title')
<title>Add Forums - {{ __('staff.staff-dashboard') }} - {{ config('other.title') }}</title>
@endsection
@section('meta')
<meta name="description" content="Add Forums - {{ __('staff.staff-dashboard') }}" />
@endsection
@section('breadcrumbs')
<li class="breadcrumbV2">
<a href="{{ route('staff.dashboard.index') }}" class="breadcrumb__link">
{{ __('staff.staff-dashboard') }}
</a>
</li>
<li class="breadcrumbV2">
<a href="{{ route('staff.forum_categories.index') }}" class="breadcrumb__link">
Forum Categories
</a>
</li>
<li class="breadcrumb--active">
{{ __('common.new-adj') }}
</li>
@endsection
@section('page', 'page__forum-category-admin--create')
@section('main')
<section class="panelV2">
<h2 class="panel__heading">Add a new Forum Category</h2>
<div class="panel__body">
<form class="form" method="POST" action="{{ route('staff.forum_categories.store') }}">
@csrf
<p class="form__group">
<input id="name" class="form__text" type="text" name="name" required />
<label class="form__label form__label--floating" for="name">Title</label>
</p>
<p class="form__group">
<input
id="position"
class="form__text"
inputmode="numeric"
name="position"
pattern="[0-9]*"
required
type="text"
/>
<label class="form__label form__label--floating" for="position">
{{ __('common.position') }}
</label>
</p>
<p class="form__group">
<textarea
id="description"
class="form__textarea"
name="description"
required
></textarea>
<label class="form__label form__label--floating" for="description">
Description
</label>
</p>
<p class="form__group">
<button class="form__button form__button--filled">Save Forum</button>
</p>
</form>
</div>
</section>
@endsection
@@ -0,0 +1,90 @@
@extends('layout.default')
@section('title')
<title>
{{ __('common.edit') }} Forums - {{ __('staff.staff-dashboard') }} -
{{ config('other.title') }}
</title>
@endsection
@section('meta')
<meta
name="description"
content="{{ __('common.edit') }} Forums - {{ __('staff.staff-dashboard') }}"
/>
@endsection
@section('breadcrumbs')
<li class="breadcrumbV2">
<a href="{{ route('staff.dashboard.index') }}" class="breadcrumb__link">
{{ __('staff.staff-dashboard') }}
</a>
</li>
<li class="breadcrumbV2">
<a href="{{ route('staff.forum_categories.index') }}" class="breadcrumb__link">
{{ __('staff.forums') }}
</a>
</li>
<li class="breadcrumbV2">
{{ $forumCategory->name }}
</li>
<li class="breadcrumb--active">
{{ __('common.edit') }}
</li>
@endsection
@section('page', 'page__forums-admin--edit')
@section('main')
<section class="panelV2">
<h2 class="panel__heading">{{ __('common.edit') }} {{ __('forum.forum') }}</h2>
<div class="panel__body">
<form
class="form"
method="POST"
action="{{ route('staff.forum_categories.update', ['forumCategory' => $forumCategory]) }}"
>
@csrf
@method('PATCH')
<p class="form__group">
<input
id="name"
class="form__text"
type="text"
name="name"
required
value="{{ $forumCategory->name }}"
/>
<label class="form__label form__label--floating" for="name">Title</label>
</p>
<p class="form__group">
<input
id="position"
class="form__text"
inputmode="numeric"
name="position"
pattern="[0-9]*"
placeholder=" "
type="text"
value="{{ $forumCategory->position }}"
required
/>
<label class="form__label form__label--floating" for="position">
{{ __('common.position') }}
</label>
</p>
<p class="form__group">
<textarea id="description" name="description" class="form__textarea" required>
{{ $forumCategory->description }}</textarea
>
<label class="form__label form__label--floating" for="description">
Description
</label>
</p>
<p class="form__group">
<button class="form__button form__button--filled">Save Forum</button>
</p>
</form>
</div>
</section>
@endsection
@@ -0,0 +1,144 @@
@extends('layout.default')
@section('title')
<title>Forums - {{ __('staff.staff-dashboard') }} - {{ config('other.title') }}</title>
@endsection
@section('meta')
<meta name="description" content="Forums - {{ __('staff.staff-dashboard') }}" />
@endsection
@section('breadcrumbs')
<li class="breadcrumbV2">
<a href="{{ route('staff.dashboard.index') }}" class="breadcrumb__link">
{{ __('staff.staff-dashboard') }}
</a>
</li>
<li class="breadcrumb--active">
{{ __('staff.forums') }}
</li>
@endsection
@section('page', 'page__forums-admin--index')
@section('main')
@foreach ($categories as $category)
<section class="panelV2">
<header class="panel__header">
<h2 class="panel__heading">{{ $category->name }}</h2>
<div class="panel__actions">
<div class="panel__action">
<a
href="{{ route('staff.forums.create', ['forumCategoryId' => $category->id]) }}"
class="form__button form__button--text"
>
{{ __('common.add') }}
</a>
</div>
<div class="panel__action">
<a
href="{{ route('staff.forum_categories.edit', ['forumCategory' => $category]) }}"
class="form__button form__button--text"
>
{{ __('common.edit') }}
</a>
</div>
<form
action="{{ route('staff.forum_categories.destroy', ['forumCategory' => $category]) }}"
method="POST"
style="display: contents"
x-data="confirmation"
>
@csrf
@method('DELETE')
<button
class="form__button form__button--text"
x-on:click.prevent="confirmAction"
data-b64-deletion-message="{{ base64_encode('Are you sure you want to delete this forum category (' . $category->name . ') and all forums, topics, and posts within?') }}"
>
{{ __('common.delete') }}
</button>
</form>
</div>
</header>
<div class="data-table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>{{ __('common.position') }}</th>
<th>{{ __('common.name') }}</th>
<th>{{ __('common.action') }}</th>
</tr>
</thead>
<tbody>
@foreach ($category->forums as $forum)
<tr>
<td>{{ $forum->position }}</td>
<td>
<a
href="{{ route('staff.forums.edit', ['forum' => $forum]) }}"
>
{{ $forum->name }}
</a>
</td>
<td>
<menu class="data-table__actions">
<li class="data-table__action">
<a
class="form__button form__button--text"
href="{{ route('staff.forums.edit', ['forum' => $forum]) }}"
>
{{ __('common.edit') }}
</a>
</li>
<li class="data-table__action">
<form
method="POST"
action="{{ route('staff.forums.destroy', ['forum' => $forum]) }}"
x-data="confirmation"
>
@csrf
@method('DELETE')
<button
x-on:click.prevent="confirmAction"
data-b64-deletion-message="{{ base64_encode('Are you sure you want to delete this forum (' . $forum->name . ') including all topics and posts within?') }}"
class="form__button form__button--text"
>
{{ __('common.delete') }}
</button>
</form>
</li>
</menu>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</section>
@endforeach
@endsection
@section('sidebar')
<section class="panelV2">
<h2 class="panel__heading">{{ __('common.actions') }}</h2>
<div class="panel__body">
<p class="form__group form__group--horizontal">
<a
href="{{ route('staff.forum_categories.create') }}"
class="form__button form__button--filled form__button--centered"
>
Create new category
</a>
</p>
<p class="form__group form__group--horizontal">
<a
href="{{ route('staff.forums.create') }}"
class="form__button form__button--filled form__button--centered"
>
Create new forum
</a>
</p>
</div>
</section>
@endsection
+79 -63
View File
@@ -15,13 +15,11 @@
</a>
</li>
<li class="breadcrumbV2">
<a href="{{ route('staff.forums.index') }}" class="breadcrumb__link">
<a href="{{ route('staff.forum_categories.index') }}" class="breadcrumb__link">
{{ __('staff.forums') }}
</a>
</li>
<li class="breadcrumb--active">
{{ __('common.new-adj') }}
</li>
<li class="breadcrumb--active">{{ __('common.new-adj') }}</li>
@endsection
@section('page', 'page__forums-admin--create')
@@ -32,43 +30,10 @@
<div class="panel__body">
<form class="form" method="POST" action="{{ route('staff.forums.store') }}">
@csrf
<p class="form__group">
<select id="forum_type" class="form__select" name="forum_type" required>
<option class="form__option" value="category">Category</option>
<option class="form__option" value="forum">Forum</option>
</select>
<label class="form__label form__label--floating" for="forum_type">
Forum Type
</label>
</p>
<p class="form__group">
<input id="name" class="form__text" type="text" name="name" required />
<label class="form__label form__label--floating" for="name">Title</label>
</p>
<p class="form__group">
<textarea
id="description"
class="form__textarea"
name="description"
placeholder=" "
></textarea>
<label class="form__label form__label--floating" for="description">
Description
</label>
</p>
<p class="form__group">
<select id="parent_id" class="form__select" name="parent_id">
<option value="">New Category</option>
@foreach ($categories as $category)
<option class="form__option" value="{{ $category->id }}">
New Forum In {{ $category->name }} Category
</option>
@endforeach
</select>
<label class="form__label form__label--floating" for="parent_id">
Parent forum
</label>
</p>
<p class="form__group">
<input
id="position"
@@ -76,13 +41,48 @@
inputmode="numeric"
name="position"
pattern="[0-9]*"
placeholder=" "
required
type="text"
/>
<label class="form__label form__label--floating" for="position">
{{ __('common.position') }}
</label>
</p>
<p class="form__group">
<textarea
id="description"
class="form__textarea"
name="description"
required
></textarea>
<label class="form__label form__label--floating" for="description">
Description
</label>
</p>
<p class="form__group">
<select
id="forum_category_id"
name="forum_category_id"
class="form__select"
x-data="{ selected: {{ $forumCategoryId }} || '' }"
x-model="selected"
x-bind:class="selected === '' ? 'form__selected--default' : ''"
required
>
<option disabled hidden></option>
@foreach ($categories as $category)
<option
value="{{ $category->id }}"
@selected($category->id === $forumCategoryId)
>
{{ $category->name }}
</option>
@endforeach
</select>
<label class="form__label form__label--floating" for="forum_category_id">
Forum Category
</label>
</p>
<div class="form__group">
<h3>Permissions</h3>
<div class="data-table-wrapper" x-data="checkboxGrid">
@@ -98,36 +98,52 @@
<tbody>
@foreach ($groups as $group)
<tr>
<th x-bind="rowHeader">{{ $group->name }}</th>
<th x-bind="rowHeader">
{{ $group->name }}
<input
type="hidden"
name="permissions[{{ $loop->index }}][group_id]"
value="{{ $group->id }}"
/>
</th>
<td>
<label>
<input
type="checkbox"
name="permissions[{{ $group->id }}][read_topic]"
value="1"
checked
/>
</label>
<input
type="hidden"
name="permissions[{{ $loop->index }}][read_topic]"
value="0"
/>
<input
type="checkbox"
name="permissions[{{ $loop->index }}][read_topic]"
value="1"
checked
/>
</td>
<td>
<label>
<input
type="checkbox"
name="permissions[{{ $group->id }}][start_topic]"
value="1"
checked
/>
</label>
<input
type="hidden"
name="permissions[{{ $loop->index }}][start_topic]"
value="0"
/>
<input
type="checkbox"
name="permissions[{{ $loop->index }}][start_topic]"
value="1"
checked
/>
</td>
<td>
<label>
<input
type="checkbox"
name="permissions[{{ $group->id }}][reply_topic]"
value="1"
checked
/>
</label>
<input
type="hidden"
name="permissions[{{ $loop->index }}][reply_topic]"
value="0"
/>
<input
type="checkbox"
name="permissions[{{ $loop->index }}][reply_topic]"
value="1"
checked
/>
</td>
</tr>
@endforeach
+59 -43
View File
@@ -21,7 +21,15 @@
</a>
</li>
<li class="breadcrumbV2">
<a href="{{ route('staff.forums.index') }}" class="breadcrumb__link">
<a href="{{ route('staff.forum_categories.index') }}" class="breadcrumb__link">
Forum Categories
</a>
</li>
<li class="breadcrumbV2">
{{ $forum->category->name }}
</li>
<li class="breadcrumbV2">
<a href="{{ route('staff.forum_categories.index') }}" class="breadcrumb__link">
{{ __('staff.forums') }}
</a>
</li>
@@ -53,47 +61,10 @@
type="text"
name="name"
value="{{ $forum->name }}"
required
/>
<label class="form__label form__label--floating" for="name">Title</label>
</p>
<p class="form__group">
<textarea id="description" name="description" class="form__textarea">
{{ $forum->description }}</textarea
>
<label class="form__label form__label--floating" for="description">
Description
</label>
</p>
<p class="form__group">
<select id="forum_type" name="forum_type" class="form__select">
@if ($forum->category == null)
<option value="category" selected>Category (Current)</option>
<option value="forum">Forum</option>
@else
<option value="category">Category</option>
<option value="forum" selected>Forum (Current)</option>
@endif
</select>
<label class="form__label form__label--floating" for="forum_type">
Forum Type
</label>
</p>
<p class="form__group">
<select id="parent_id" name="parent_id" class="form__select">
@if ($forum->category != null)
<option value="{{ $forum->parent_id }}" selected>
{{ $forum->category->name }} (Current)
</option>
@endif
@foreach ($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
<label class="form__label form__label--floating" for="parent_id">
Parent forum
</label>
</p>
<p class="form__group">
<input
id="position"
@@ -110,6 +81,29 @@
{{ __('common.position') }}
</label>
</p>
<p class="form__group">
<textarea id="description" name="description" class="form__textarea" required>
{{ $forum->description }}</textarea
>
<label class="form__label form__label--floating" for="description">
Description
</label>
</p>
<p class="form__group">
<select id="forum_category_id" name="forum_category_id" class="form__select">
@foreach ($categories as $category)
<option
value="{{ $category->id }}"
@selected($category->id === $forum->category->id)
>
{{ $category->name }}
</option>
@endforeach
</select>
<label class="form__label form__label--floating" for="forum_category_id">
Forum Category
</label>
</p>
<div class="form__group">
<label class="form__label">Permissions</label>
<div class="data-table-wrapper">
@@ -125,27 +119,49 @@
<tbody x-ref="tbody">
@foreach ($groups as $group)
<tr>
<th x-bind="rowHeader">{{ $group->name }}</th>
<th x-bind="rowHeader">
{{ $group->name }}
<input
type="hidden"
name="permissions[{{ $loop->index }}][group_id]"
value="{{ $group->id }}"
/>
</th>
<td>
<input
type="hidden"
name="permissions[{{ $loop->index }}][read_topic]"
value="0"
/>
<input
type="checkbox"
name="permissions[{{ $group->id }}][read_topic]"
name="permissions[{{ $loop->index }}][read_topic]"
value="1"
@checked($forum->permissions->where('group_id', '=', $group->id)->first()?->read_topic)
/>
</td>
<td>
<input
type="hidden"
name="permissions[{{ $loop->index }}][start_topic]"
value="0"
/>
<input
type="checkbox"
name="permissions[{{ $group->id }}][start_topic]"
name="permissions[{{ $loop->index }}][start_topic]"
value="1"
@checked($forum->permissions->where('group_id', '=', $group->id)->first()?->start_topic)
/>
</td>
<td>
<input
type="hidden"
name="permissions[{{ $loop->index }}][reply_topic]"
value="0"
/>
<input
type="checkbox"
name="permissions[{{ $group->id }}][reply_topic]"
name="permissions[{{ $loop->index }}][reply_topic]"
value="1"
@checked($forum->permissions->where('group_id', '=', $group->id)->first()?->reply_topic)
/>
-129
View File
@@ -1,129 +0,0 @@
@extends('layout.default')
@section('title')
<title>Forums - {{ __('staff.staff-dashboard') }} - {{ config('other.title') }}</title>
@endsection
@section('meta')
<meta name="description" content="Forums - {{ __('staff.staff-dashboard') }}" />
@endsection
@section('breadcrumbs')
<li class="breadcrumbV2">
<a href="{{ route('staff.dashboard.index') }}" class="breadcrumb__link">
{{ __('staff.staff-dashboard') }}
</a>
</li>
<li class="breadcrumb--active">
{{ __('staff.forums') }}
</li>
@endsection
@section('page', 'page__forums-admin--index')
@section('main')
<section class="panelV2">
<header class="panel__header">
<h2 class="panel__heading">Forums</h2>
<a href="{{ route('staff.forums.create') }}" class="form__button form__button--text">
{{ __('common.add') }}
</a>
</header>
<div class="data-table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>{{ __('common.name') }}</th>
<th>Type</th>
<th>{{ __('common.position') }}</th>
<th>{{ __('common.action') }}</th>
</tr>
</thead>
<tbody>
@foreach ($categories as $category)
<tr>
<td>
<a href="{{ route('staff.forums.edit', ['forum' => $category]) }}">
{{ $category->name }}
</a>
</td>
<td>Category</td>
<td>{{ $category->position }}</td>
<td>
<menu class="data-table__actions">
<li class="data-table__action">
<a
class="form__button form__button--text"
href="{{ route('staff.forums.edit', ['forum' => $category]) }}"
>
{{ __('common.edit') }}
</a>
</li>
<li class="data-table__action">
<form
method="POST"
action="{{ route('staff.forums.destroy', ['forum' => $category]) }}"
x-data="confirmation"
>
@csrf
@method('DELETE')
<button
x-on:click.prevent="confirmAction"
data-b64-deletion-message="{{ base64_encode('Are you sure you want to delete this forum category: ' . $category->name . '?') }}"
class="form__button form__button--text"
>
{{ __('common.delete') }}
</button>
</form>
</li>
</menu>
</td>
</tr>
@foreach ($category->forums as $forum)
<tr>
<td style="padding-left: 50px">
<a
href="{{ route('staff.forums.edit', ['forum' => $forum]) }}"
>
{{ $forum->name }}
</a>
</td>
<td>Forum</td>
<td>{{ $forum->position }}</td>
<td>
<menu class="data-table__actions">
<li class="data-table__action">
<a
class="form__button form__button--text"
href="{{ route('staff.forums.edit', ['forum' => $forum]) }}"
>
{{ __('common.edit') }}
</a>
</li>
<li class="data-table__action">
<form
method="POST"
action="{{ route('staff.forums.destroy', ['forum' => $forum]) }}"
x-data="confirmation"
>
@csrf
@method('DELETE')
<button
x-on:click.prevent="confirmAction"
data-b64-deletion-message="{{ base64_encode('Are you sure you want to delete this forum: ' . $forum->name . '?') }}"
class="form__button form__button--text"
>
{{ __('common.delete') }}
</button>
</form>
</li>
</menu>
</td>
</tr>
@endforeach
@endforeach
</tbody>
</table>
</div>
</section>
@endsection
@@ -1,7 +1,7 @@
@extends('layout.default')
@section('title')
<title>{{ $forum->name }} - {{ __('forum.forums') }} - {{ config('other.title') }}</title>
<title>{{ $category->name }} - {{ __('forum.forums') }} - {{ config('other.title') }}</title>
@endsection
@section('meta')
@@ -15,7 +15,7 @@
</a>
</li>
<li class="breadcrumb--active">
{{ $forum->name }}
{{ $category->name }}
</li>
@endsection
@@ -26,5 +26,5 @@
@section('page', 'page__forum--category')
@section('main')
@livewire('forum-category-topic-search', ['category' => $forum])
@livewire('forum-category-topic-search', ['category' => $category])
@endsection
+2 -2
View File
@@ -31,9 +31,9 @@
{{ $category->name }}
</a>
</h2>
@if ($category->forums->count() > 0)
@if ($category->forums->isNotEmpty())
<ul class="subforum-listings">
@foreach ($category->forums->sortBy('position') as $forum)
@foreach ($category->forums as $forum)
<li class="subforum-listings__item">
<x-forum.subforum-listing :subforum="$forum" />
</li>
+3 -3
View File
@@ -64,9 +64,9 @@
</p>
<p class="form__group">
<select id="forum_id" name="forum_id" class="form__select">
@foreach ($categories as $category)
<optgroup label="{{ $category->name }}">
@foreach ($category->forums as $forum)
@foreach ($categories as $name => $forums)
<optgroup label="{{ $name }}">
@foreach ($forums as $forum)
<option
value="{{ $forum->id }}"
@selected($topic->forum_id === $forum->id)
+5 -5
View File
@@ -12,15 +12,15 @@
</li>
<li class="breadcrumbV2">
<a
href="{{ route('forums.categories.show', ['id' => $forum->category->id]) }}"
href="{{ route('forums.categories.show', ['id' => $topic->forum->category->id]) }}"
class="breadcrumb__link"
>
{{ $forum->category->name }}
{{ $topic->forum->category->name }}
</a>
</li>
<li class="breadcrumbV2">
<a href="{{ route('forums.show', ['id' => $forum->id]) }}" class="breadcrumb__link">
{{ $forum->name }}
<a href="{{ route('forums.show', ['id' => $topic->forum->id]) }}" class="breadcrumb__link">
{{ $topic->forum->name }}
</a>
</li>
<li class="breadcrumb--active">
@@ -43,7 +43,7 @@
</p>
@endif
@if (($topic->state === 'open' && $forum->getPermission()?->reply_topic) || auth()->user()->group->is_modo)
@if (($topic->state === 'open' && $topic->forum->getPermission()?->reply_topic) || auth()->user()->group->is_modo)
<form id="forum_reply_form" method="POST" action="{{ route('posts.store') }}">
@csrf
<input type="hidden" name="topic_id" value="{{ $topic->id }}" />
@@ -42,15 +42,15 @@
wire:model="forumId"
>
<option value="">Any</option>
@foreach ($forumCategories->sortBy('position') as $category)
<option value="{{ $category->id }}">
{{ $category->name }}
</option>
@foreach ($category->forums->sortBy('position') as $forum)
<option value="{{ $forum->id }}">
&raquo; {{ $forum->name }}
</option>
@endforeach
@foreach ($forumCategories as $category)
<optgroup label="{{ $category->name }}">
@foreach ($category->forums as $forum)
<option value="{{ $forum->id }}">
{{ $forum->name }}
</option>
@endforeach
</optgroup>
@endforeach
</select>
<label class="form__label form__label--floating" for="category">
+11 -1
View File
@@ -844,9 +844,19 @@ Route::middleware('language')->group(function (): void {
});
// Forums System
Route::prefix('forum-categories')->middleware('admin')->group(function (): void {
Route::name('forum_categories.')->group(function (): void {
Route::get('/', [App\Http\Controllers\Staff\ForumCategoryController::class, 'index'])->name('index');
Route::get('/create', [App\Http\Controllers\Staff\ForumCategoryController::class, 'create'])->name('create');
Route::post('/', [App\Http\Controllers\Staff\ForumCategoryController::class, 'store'])->name('store');
Route::get('/{forumCategory}/edit', [App\Http\Controllers\Staff\ForumCategoryController::class, 'edit'])->name('edit');
Route::patch('/{forumCategory}', [App\Http\Controllers\Staff\ForumCategoryController::class, 'update'])->name('update');
Route::delete('/{forumCategory}', [App\Http\Controllers\Staff\ForumCategoryController::class, 'destroy'])->name('destroy');
});
});
Route::prefix('forums')->middleware('admin')->group(function (): void {
Route::name('forums.')->group(function (): void {
Route::get('/', [App\Http\Controllers\Staff\ForumController::class, 'index'])->name('index');
Route::get('/create', [App\Http\Controllers\Staff\ForumController::class, 'create'])->name('create');
Route::post('/', [App\Http\Controllers\Staff\ForumController::class, 'store'])->name('store');
Route::get('/{forum}/edit', [App\Http\Controllers\Staff\ForumController::class, 'edit'])->name('edit');
@@ -18,21 +18,12 @@ use App\Models\User;
test('show returns an ok response', function (): void {
$user = User::factory()->create();
// This forum does not have a parent_id, which makes it a "Forum Category".
$parentForum = Forum::factory()->create([
'parent_id' => null,
'last_topic_id' => null,
]);
$forum = Forum::factory()->create();
Permission::factory()->create([
'forum_id' => $parentForum->id,
'group_id' => $user->group_id,
'forum_id' => $forum->id,
]);
// This forum has a parent_id, which makes it a "Forum".
$forum = Forum::factory()->create([
'parent_id' => $parentForum->id,
'last_topic_id' => null,
]);
$this->actingAs($user)->get(route('forums.categories.show', ['id' => $forum->id]));
$this->actingAs($user)->get(route('forums.categories.show', ['id' => $forum->forum_category_id]));
});
@@ -36,16 +36,16 @@ test('show returns an ok response', function (): void {
$user = User::factory()->create();
$forum = Forum::factory()->create([
'parent_id' => null, // This Forum does not have a parent, which makes it a "Forum Category".
'last_post_user_id' => $user->id,
'last_topic_id' => null,
]);
Permission::factory()->create([
'group_id' => $user->group_id,
'forum_id' => $forum->id,
'read_topic' => true,
]);
$response = $this->actingAs($user)->get(route('forums.show', ['id' => $forum->id]));
$response->assertRedirect(route('forums.categories.show', ['id' => $forum->id]));
$response->assertViewIs('forum.forum_topic.index');
});
@@ -69,21 +69,6 @@ test('edit returns an ok response', function (): void {
// TODO: perform additional assertions
});
test('index returns an ok response', function (): void {
$this->markTestIncomplete('This test case was generated by Shift. When you are ready, remove this line and complete this test case.');
$forums = Forum::factory()->times(3)->create();
$user = User::factory()->create();
$response = $this->actingAs($user)->get(route('staff.forums.index'));
$response->assertOk();
$response->assertViewIs('Staff.forum.index');
$response->assertViewHas('categories');
// TODO: perform additional assertions
});
test('store validates with a form request', function (): void {
$this->assertActionUsesFormRequest(
ForumController::class,
+2 -7
View File
@@ -71,16 +71,11 @@ final class ForumControllerTest extends TestCase
$user = User::factory()->create();
// This Forum does not have a parent, which makes it a proper Forum
// (and not a "Forum Category").
$forum = Forum::factory()->create([
'parent_id' => 0,
'last_post_user_id' => $user->id,
]);
$forum = Forum::factory()->create();
$permissions = Permission::factory()->create([
'forum_id' => $forum->id,
'group_id' => $user->group_id,
'read_topic' => true,
]);
@@ -33,32 +33,38 @@ test('rules', function (): void {
'position' => [
'required',
],
'slug' => [
'required',
],
'description' => [
'required',
],
'parent_id' => [
'sometimes',
'nullable',
'integer',
'forum_category_id' => [
'required',
'exists:forum_categories,id',
],
'permissions' => [
'sometimes',
'required',
'array',
],
'permissions.*' => [
'sometimes',
'required',
'array:group_id,read_topic,reply_topic,start_topic',
],
'permissions.*.group_id' => [
'required',
'exists:groups,id',
],
'permissions.*.read_topic' => [
'sometimes',
'required',
'boolean',
],
'permissions.*.reply_topic' => [
'sometimes',
'required',
'boolean',
],
'permissions.*.start_topic' => [
'sometimes',
'required',
'boolean',
],
], $actual);
@@ -33,31 +33,39 @@ test('rules', function (): void {
'position' => [
'required',
],
'slug' => [
'required',
],
'description' => [
'required',
],
'parent_id' => [
'sometimes',
'nullable',
'integer',
'forum_category_id' => [
'required',
'exists:forum_categories,id',
],
'permissions' => [
'required',
'array',
],
'permissions.*' => [
'required',
'array:group_id,read_topic,reply_topic,start_topic',
],
'permissions.*.group_id' => [
'required',
'exists:groups,id',
],
'permissions.*.read_topic' => [
'required',
'boolean',
],
'permissions.*.reply_topic' => [
'required',
'boolean',
],
'permissions.*.start_topic' => [
'required',
'boolean',
],
'forum_type' => [
'in:category,forum',
],
], $actual);
});