diff --git a/app/Actions/Discussion/ConvertDiscussionToThreadAction.php b/app/Actions/Discussion/ConvertDiscussionToThreadAction.php
new file mode 100644
index 00000000..2e16668f
--- /dev/null
+++ b/app/Actions/Discussion/ConvertDiscussionToThreadAction.php
@@ -0,0 +1,36 @@
+ $discussion->title,
+ 'slug' => $discussion->slug,
+ 'body' => $discussion->body,
+ 'user_id' => $discussion->user_id,
+ 'last_posted_at' => null,
+ ]);
+
+ $discussion->replies()->update([
+ 'replyable_type' => 'thread',
+ 'replyable_id' => $thread->id,
+ ]);
+
+ $discussion->delete();
+
+ app(NotifyUsersOfThreadConversion::class)->execute($thread);
+
+ return $thread;
+ });
+ }
+}
diff --git a/app/Actions/Discussion/NotifyUsersOfThreadConversion.php b/app/Actions/Discussion/NotifyUsersOfThreadConversion.php
new file mode 100644
index 00000000..cb8ce3bb
--- /dev/null
+++ b/app/Actions/Discussion/NotifyUsersOfThreadConversion.php
@@ -0,0 +1,25 @@
+replies()->pluck('user_id')->unique()->toArray();
+
+ User::whereIn('id', $usersToNotify)->get()->each->notify(new ThreadConvertedByCreator($thread));
+
+ if (Auth::check() && Auth::user()->isAdmin()) {
+ $thread->user->notify(new ThreadConvertedByAdmin($thread));
+ }
+ }
+}
diff --git a/app/Filament/Resources/ArticleResource.php b/app/Filament/Resources/ArticleResource.php
index a5ea0ba5..e66a284c 100644
--- a/app/Filament/Resources/ArticleResource.php
+++ b/app/Filament/Resources/ArticleResource.php
@@ -25,7 +25,7 @@ final class ArticleResource extends Resource
protected static ?string $navigationIcon = 'heroicon-o-newspaper';
- public static function getNavigationGroup(): ?string
+ public static function getNavigationGroup(): string
{
return __('Contenu');
}
diff --git a/app/Filament/Resources/ArticleResource/Pages/ListArticles.php b/app/Filament/Resources/ArticleResource/Pages/ListArticles.php
index 4341d4ed..a793cae9 100644
--- a/app/Filament/Resources/ArticleResource/Pages/ListArticles.php
+++ b/app/Filament/Resources/ArticleResource/Pages/ListArticles.php
@@ -13,7 +13,7 @@ final class ListArticles extends ListRecords
{
protected static string $resource = ArticleResource::class;
- public function isTableRecordSelectable(): ?Closure
+ public function isTableRecordSelectable(): Closure
{
return fn (Article $record): bool => $record->isNotPublished();
}
diff --git a/app/Filament/Resources/ChannelResource.php b/app/Filament/Resources/ChannelResource.php
index 9f967ba5..4706efb7 100644
--- a/app/Filament/Resources/ChannelResource.php
+++ b/app/Filament/Resources/ChannelResource.php
@@ -23,7 +23,7 @@ final class ChannelResource extends Resource
protected static ?string $navigationIcon = 'untitledui-git-branch';
- public static function getNavigationGroup(): ?string
+ public static function getNavigationGroup(): string
{
return __('Forum');
}
diff --git a/app/Filament/Resources/DiscussionResource.php b/app/Filament/Resources/DiscussionResource.php
index 99ab6fd0..3f68d24b 100644
--- a/app/Filament/Resources/DiscussionResource.php
+++ b/app/Filament/Resources/DiscussionResource.php
@@ -18,7 +18,7 @@ final class DiscussionResource extends Resource
protected static ?string $navigationIcon = 'untitledui-message-chat-square';
- public static function getNavigationGroup(): ?string
+ public static function getNavigationGroup(): string
{
return __('Contenu');
}
diff --git a/app/Filament/Resources/TagResource.php b/app/Filament/Resources/TagResource.php
index a77dc3f4..e7950e84 100644
--- a/app/Filament/Resources/TagResource.php
+++ b/app/Filament/Resources/TagResource.php
@@ -20,7 +20,7 @@ final class TagResource extends Resource
protected static ?string $navigationIcon = 'untitledui-tag-03';
- public static function getNavigationGroup(): ?string
+ public static function getNavigationGroup(): string
{
return __('Contenu');
}
diff --git a/app/Filament/Resources/UserResource.php b/app/Filament/Resources/UserResource.php
index ca29f1c8..11a77cbc 100644
--- a/app/Filament/Resources/UserResource.php
+++ b/app/Filament/Resources/UserResource.php
@@ -23,7 +23,7 @@ final class UserResource extends Resource
protected static ?string $navigationIcon = 'untitledui-users-02';
- public static function getNavigationGroup(): ?string
+ public static function getNavigationGroup(): string
{
return __('Management');
}
diff --git a/app/Livewire/Components/Forum/ReplyForm.php b/app/Livewire/Components/Forum/ReplyForm.php
index 82731778..56814e2f 100644
--- a/app/Livewire/Components/Forum/ReplyForm.php
+++ b/app/Livewire/Components/Forum/ReplyForm.php
@@ -41,7 +41,7 @@ public function open(?int $replyId = null): void
{
$this->reply = Reply::query()->find($replyId);
- $this->form->fill(['body' => $this->reply?->body ?? '']);
+ $this->form->fill(['body' => $this->reply->body ?? '']);
$this->show = true;
}
diff --git a/app/Livewire/Modals/ConvertDiscussion.php b/app/Livewire/Modals/ConvertDiscussion.php
new file mode 100644
index 00000000..1ed52138
--- /dev/null
+++ b/app/Livewire/Modals/ConvertDiscussion.php
@@ -0,0 +1,32 @@
+findOrFail($this->discussionId);
+
+ $this->authorize('convertedToThread', $discussion);
+
+ $thread = app(ConvertDiscussionToThreadAction::class)->execute($discussion);
+
+ $this->redirectRoute('forum.show', $thread, navigate: true);
+ }
+
+ public function render(): View
+ {
+ return view('livewire.modals.convert-discussion');
+ }
+}
diff --git a/app/Livewire/Modals/Unsplash.php b/app/Livewire/Modals/Unsplash.php
index 3fb57808..142f066a 100644
--- a/app/Livewire/Modals/Unsplash.php
+++ b/app/Livewire/Modals/Unsplash.php
@@ -20,6 +20,6 @@ public static function modalMaxWidth(): string
public function render(): View
{
- return view('livewire.modals.unsplash');
+ return view('livewire.modals.unsplash'); // @phpstan-ignore-line
}
}
diff --git a/app/Mail/NewReplyEmail.php b/app/Mail/NewReplyEmail.php
index 6f8cde7e..a71402f6 100644
--- a/app/Mail/NewReplyEmail.php
+++ b/app/Mail/NewReplyEmail.php
@@ -21,7 +21,6 @@ public function __construct(
public function build(): self
{
- // @phpstan-ignore-next-line
return $this->subject("Re: {$this->reply->replyAble->subject()}")
->markdown('emails.new_reply');
}
diff --git a/app/Models/Discussion.php b/app/Models/Discussion.php
index a2fded4f..2a293e42 100644
--- a/app/Models/Discussion.php
+++ b/app/Models/Discussion.php
@@ -68,6 +68,7 @@ final class Discussion extends Model implements ReactableInterface, ReplyInterfa
];
protected $appends = [
+ // @phpstan-ignore-next-line
'count_all_replies_with_child',
];
diff --git a/app/Models/User.php b/app/Models/User.php
index d2bffbb1..2be01392 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -183,7 +183,7 @@ public function getFilamentAvatarUrl(): ?string
}
/**
- * @return array{name: string, username: string, picture: string}
+ * @return array{name: string, username: string, picture: string|null}
*/
public function profile(): array
{
@@ -343,6 +343,7 @@ public function hasPassword(): bool
{
$password = $this->getAuthPassword();
+ // @phpstan-ignore-next-line
return $password !== '' && $password !== null;
}
@@ -382,12 +383,12 @@ public function countReplies(): int
public function countSolutions(): int
{
- return $this->replyAble()->isSolution()->count();
+ return $this->replyAble()->isSolution()->count(); // @phpstan-ignore-line
}
public function countArticles(): int
{
- return $this->articles()->approved()->count();
+ return $this->articles()->approved()->count(); // @phpstan-ignore-line
}
public function countDiscussions(): int
diff --git a/app/Notifications/ThreadConvertedByAdmin.php b/app/Notifications/ThreadConvertedByAdmin.php
new file mode 100644
index 00000000..dc84cda4
--- /dev/null
+++ b/app/Notifications/ThreadConvertedByAdmin.php
@@ -0,0 +1,41 @@
+
+ */
+ public function via(object $notifiable): array
+ {
+ return ['mail'];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ */
+ public function toMail(object $notifiable): MailMessage
+ {
+ return (new MailMessage)
+ ->subject(__('pages/discussion.converted_by_admin.subject'))
+ ->greeting(__('pages/discussion.converted_by_admin.greeting'))
+ ->line(__('pages/discussion.converted_by_admin.converted_line'))
+ ->line(__('pages/discussion.converted_by_admin.thread_title').$this->thread->title)
+ ->action(__('pages/discussion.converted_by_admin.action_text'), route('forum.show', $this->thread))
+ ->line(__('pages/discussion.converted_by_admin.admin_action_line'));
+ }
+}
diff --git a/app/Notifications/ThreadConvertedByCreator.php b/app/Notifications/ThreadConvertedByCreator.php
new file mode 100644
index 00000000..89eb7b66
--- /dev/null
+++ b/app/Notifications/ThreadConvertedByCreator.php
@@ -0,0 +1,40 @@
+
+ */
+ public function via(object $notifiable): array
+ {
+ return ['mail'];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ */
+ public function toMail(object $notifiable): MailMessage
+ {
+ return (new MailMessage)
+ ->subject(__('pages/discussion.converted_by_creator'))
+ ->line(__('pages/discussion.converted_by_creator.converted_line'))
+ ->line(__('pages/discussion.converted_by_creator.thread_title').$this->thread->title)
+ ->action(__('pages/discussion.converted_by_creator.action_text'), route('forum.show', $this->thread))
+ ->line(__('pages/discussion.converted_by_creator.thank_you_line'));
+ }
+}
diff --git a/app/Policies/DiscussionPolicy.php b/app/Policies/DiscussionPolicy.php
index 274518de..7ee28b4b 100644
--- a/app/Policies/DiscussionPolicy.php
+++ b/app/Policies/DiscussionPolicy.php
@@ -56,4 +56,9 @@ public function report(User $user, Discussion $discussion): bool
{
return $user->hasVerifiedEmail() && ! $discussion->isAuthoredBy($user);
}
+
+ public function convertedToThread(User $user, Discussion $discussion): bool
+ {
+ return $discussion->isAuthoredBy($user) || $user->isAdmin();
+ }
}
diff --git a/app/Policies/NotificationPolicy.php b/app/Policies/NotificationPolicy.php
index fcea43cb..bdcd5c2b 100644
--- a/app/Policies/NotificationPolicy.php
+++ b/app/Policies/NotificationPolicy.php
@@ -16,6 +16,6 @@ final class NotificationPolicy
public function markAsRead(User $user, DatabaseNotification $notification): bool
{
- return $notification->notifiable->is($user); // @phpstan-ignore-line
+ return $notification->notifiable->is($user);
}
}
diff --git a/app/Spotlight/Article.php b/app/Spotlight/Article.php
index e679c217..8a3adbb6 100644
--- a/app/Spotlight/Article.php
+++ b/app/Spotlight/Article.php
@@ -20,7 +20,7 @@ final class Article extends SpotlightCommand
protected array $synonyms = [];
- public function dependencies(): ?SpotlightCommandDependencies
+ public function dependencies(): SpotlightCommandDependencies
{
return SpotlightCommandDependencies::collection()
->add(
diff --git a/app/Spotlight/Discussion.php b/app/Spotlight/Discussion.php
index 5d1ab8f3..d5b11292 100644
--- a/app/Spotlight/Discussion.php
+++ b/app/Spotlight/Discussion.php
@@ -20,7 +20,7 @@ final class Discussion extends SpotlightCommand
protected array $synonyms = [];
- public function dependencies(): ?SpotlightCommandDependencies
+ public function dependencies(): SpotlightCommandDependencies
{
return SpotlightCommandDependencies::collection()
->add(
diff --git a/app/Spotlight/Sujet.php b/app/Spotlight/Sujet.php
index 2cf9aafb..9703a0f8 100644
--- a/app/Spotlight/Sujet.php
+++ b/app/Spotlight/Sujet.php
@@ -25,7 +25,7 @@ final class Sujet extends SpotlightCommand
'thread',
];
- public function dependencies(): ?SpotlightCommandDependencies
+ public function dependencies(): SpotlightCommandDependencies
{
return SpotlightCommandDependencies::collection()
->add(
diff --git a/app/Spotlight/User.php b/app/Spotlight/User.php
index 454be24b..6a12fb4f 100644
--- a/app/Spotlight/User.php
+++ b/app/Spotlight/User.php
@@ -20,7 +20,7 @@ final class User extends SpotlightCommand
protected array $synonyms = [];
- public function dependencies(): ?SpotlightCommandDependencies
+ public function dependencies(): SpotlightCommandDependencies
{
return SpotlightCommandDependencies::collection()
->add(
diff --git a/app/Traits/HasReplies.php b/app/Traits/HasReplies.php
index b0ae2289..68e692d3 100644
--- a/app/Traits/HasReplies.php
+++ b/app/Traits/HasReplies.php
@@ -54,7 +54,7 @@ public function isConversationOld(): bool
$sixMonthsAgo = now()->subMonths(6);
if ($reply = $this->replies()->latest()->first()) {
- /** @var $reply Reply */
+ /** @var Reply $reply */
return $reply->created_at->lt($sixMonthsAgo);
}
diff --git a/app/Traits/HasSocialite.php b/app/Traits/HasSocialite.php
index 700324ac..64eea203 100644
--- a/app/Traits/HasSocialite.php
+++ b/app/Traits/HasSocialite.php
@@ -8,6 +8,9 @@
use Laravel\Socialite\Contracts\User;
use Laravel\Socialite\Facades\Socialite;
+/**
+ * @phpstan-ignore trait.unused
+ */
trait HasSocialite
{
/**
diff --git a/app/Traits/UserResponse.php b/app/Traits/UserResponse.php
index 31de80f9..e075c99f 100644
--- a/app/Traits/UserResponse.php
+++ b/app/Traits/UserResponse.php
@@ -8,6 +8,9 @@
use App\Http\Resources\EnterpriseResource;
use App\Models\User;
+/**
+ * @phpstan-ignore trait.unused
+ */
trait UserResponse
{
/**
diff --git a/lang/en/actions.php b/lang/en/actions.php
index 7d69eb4c..cf1432cc 100644
--- a/lang/en/actions.php
+++ b/lang/en/actions.php
@@ -12,5 +12,6 @@
'save' => 'Save',
'ban' => 'Ban',
'unban' => 'Cancel ban',
+ 'confirm' => 'Confirm',
];
diff --git a/lang/en/pages/discussion.php b/lang/en/pages/discussion.php
new file mode 100644
index 00000000..a263b708
--- /dev/null
+++ b/lang/en/pages/discussion.php
@@ -0,0 +1,41 @@
+ 'Tous les sujets de discussion',
+ 'contributors' => [
+ 'top' => 'Top Contributeurs',
+ 'description' => 'Les personnes qui ont lancé le plus de discussions sur le site.',
+ ],
+ 'empty' => 'Discussions sans commentaires',
+ 'empty_description' => 'Les discussions / sujets qui n’ont pas encore eu de commentaires. Soyez le premier à apporter votre contribution.',
+ 'total_answer' => 'total réponses',
+ 'new_discussion' => 'Nouveau discussion',
+ 'filter' => [
+ 'recent' => 'Récent',
+ 'popular' => 'Populaire',
+ 'active' => 'Actif',
+ ],
+ 'comments_count' => 'Commentaires (:count)',
+ 'convert_to_thread' => 'Convert to thread',
+ 'confirm_conversion' => 'Confirm conversion',
+ 'text_confirmation' => 'Do you really want to turn this discussion into a topic?',
+ 'converted_by_admin' => [
+ 'subject' => 'Discussion Converted to Thread by the administrator',
+ 'greeting' => 'Hello!',
+ 'converted_line' => 'An admin has converted a discussion to a thread.',
+ 'thread_title' => 'Thread Title: ',
+ 'action_text' => 'View Thread',
+ 'admin_action_line' => 'This action was performed by an administrator.',
+ ],
+ 'converted_by_creator' => [
+ 'subject' => 'Discussion Converted to Thread',
+ 'converted_line' => 'A discussion you participated in has been converted to a thread.',
+ 'thread_title' => 'Thread Title: ',
+ 'action_text' => 'View Thread',
+ 'thank_you_line' => 'Thank you for your participation!',
+ ],
+
+];
diff --git a/lang/fr/actions.php b/lang/fr/actions.php
index 7c13a7a7..3b1ab0a7 100644
--- a/lang/fr/actions.php
+++ b/lang/fr/actions.php
@@ -12,5 +12,6 @@
'save' => 'Enregistrer',
'ban' => 'Bannir',
'unban' => 'Dé-bannir',
+ 'confirm' => 'Confirmer',
];
diff --git a/lang/fr/pages/discussion.php b/lang/fr/pages/discussion.php
index cc1551b9..1e6d0f7a 100644
--- a/lang/fr/pages/discussion.php
+++ b/lang/fr/pages/discussion.php
@@ -19,5 +19,23 @@
'active' => 'Actif',
],
'comments_count' => 'Commentaires (:count)',
+ 'convert_to_thread' => 'Convertir en sujet',
+ 'confirm_conversion' => 'Confirmez la conversion',
+ 'text_confirmation' => 'Voulez-vous vraiment transformer cette discussion en sujet de forum?',
+ 'converted_by_admin' => [
+ 'subject' => 'Discussion convertie en sujet par l\'administrateur',
+ 'greeting' => 'Bonjour !',
+ 'converted_line' => 'Un administrateur a converti votre discussion en sujet de forum.',
+ 'thread_title' => 'Titre du sujet : ',
+ 'action_text' => 'Voir le sujet',
+ 'admin_action_line' => 'Cette action a été effectuée par un administrateur.',
+ ],
+ 'converted_by_creator' => [
+ 'subject' => 'Discussion convertie en sujet par le createur',
+ 'converted_line' => 'Une discussion à laquelle vous avez participé a été convertie en fil de discussion.',
+ 'thread_title' => 'Titre du fil : ',
+ 'action_text' => 'Voir le fil',
+ 'thank_you_line' => 'Merci pour votre participation !',
+ ],
];
diff --git a/resources/views/livewire/modals/convert-discussion.blade.php b/resources/views/livewire/modals/convert-discussion.blade.php
new file mode 100644
index 00000000..82340bcb
--- /dev/null
+++ b/resources/views/livewire/modals/convert-discussion.blade.php
@@ -0,0 +1,35 @@
+