Issues (435)

app/Http/Controllers/InvitationController.php (3 issues)

1
<?php
2
3
namespace App\Http\Controllers;
4
5
use App\Models\Invitation;
6
use App\Models\Settings;
7
use App\Services\InvitationService;
8
use Illuminate\Http\JsonResponse;
9
use Illuminate\Http\RedirectResponse;
10
use Illuminate\Http\Request;
11
use Illuminate\View\View;
12
13
class InvitationController extends BasePageController
14
{
15
    protected InvitationService $invitationService;
16
17
    public function __construct(InvitationService $invitationService)
18
    {
19
        parent::__construct();
20
        $this->invitationService = $invitationService;
21
        $this->middleware('auth')->except(['show', 'accept']);
22
    }
23
24
    /**
25
     * Display a listing of the user's invitations
26
     */
27
    public function index(Request $request): View
28
    {
29
30
        $inviteMode = (int) Settings::settingValue('registerstatus') === Settings::REGISTER_STATUS_INVITE;
31
        $status = $request->get('status');
32
33
        $this->viewData['meta_title'] = 'My Invitations';
34
        $this->viewData['meta_keywords'] = 'invitations,invite,users,manage';
35
        $this->viewData['meta_description'] = 'Manage your sent invitations and send new invitations to friends';
36
        $this->viewData['status'] = $status;
37
38
        if (! $inviteMode) {
39
            // Invitations disabled: show informational message only, no queries.
40
            $this->viewData['invite_mode'] = false;
41
            $this->viewData['stats'] = [];
42
            $this->viewData['invitations'] = [];
43
            $this->viewData['pagination_links'] = null;
44
45
            $this->viewData['meta_title'] = 'Invitations Disabled';
46
            $this->viewData['meta_keywords'] = 'invitations,disabled';
47
            $this->viewData['meta_description'] = 'Invitations are currently disabled on this site.';
48
49
            return view('invitations.index', $this->viewData);
50
        }
51
52
        $user = auth()->user();
53
54
        $invitations = $this->invitationService->getUserInvitations($user->id, $status);
55
        $stats = $this->invitationService->getUserInvitationStats($user->id);
56
57
        // Convert paginated results to array for Blade
58
        $invitationsArray = [];
59
        foreach ($invitations as $invitation) {
60
            $invitationData = $invitation->toArray();
61
62
            // Add related user data
63
            if ($invitation->usedBy) {
64
                $invitationData['used_by_user'] = $invitation->usedBy->toArray();
65
            }
66
67
            // Convert timestamps
68
            $invitationData['created_at'] = strtotime($invitation->created_at);
69
            $invitationData['expires_at'] = strtotime($invitation->expires_at);
70
            if ($invitation->used_at) {
71
                $invitationData['used_at'] = strtotime($invitation->used_at);
72
            }
73
74
            $invitationsArray[] = $invitationData;
75
        }
76
77
        $this->viewData['invite_mode'] = true;
78
        $this->viewData['invitations'] = $invitationsArray;
79
        $this->viewData['stats'] = $stats;
80
        $this->viewData['status'] = $status;
81
        $this->viewData['pagination_links'] = $invitations->links();
82
83
        return view('invitations.index', $this->viewData);
84
    }
85
86
    /**
87
     * Show the form for creating a new invitation
88
     */
89
    public function create(): View
90
    {
91
92
        $inviteMode = (int) Settings::settingValue('registerstatus') === Settings::REGISTER_STATUS_INVITE;
93
94
        $this->viewData['meta_title'] = 'Send New Invitation';
95
        $this->viewData['meta_keywords'] = 'invitation,invite,send,new,user';
96
        $this->viewData['meta_description'] = 'Send a new invitation to invite someone to join the site';
97
98
        if (! $inviteMode) {
99
            $this->viewData['invite_mode'] = false;
100
            $this->viewData['meta_title'] = 'Invitations Disabled';
101
            $this->viewData['meta_keywords'] = 'invitations,disabled';
102
            $this->viewData['meta_description'] = 'Invitations are currently disabled on this site.';
103
104
            return view('invitations.create', $this->viewData);
105
        }
106
107
        $user = auth()->user();
108
109
        // Calculate available invites (total - active pending invitations)
110
        $activeInvitations = Invitation::where('invited_by', $user->id)
111
            ->where('is_active', true)
112
            ->where('used_at', null)
113
            ->where('expires_at', '>', now())
114
            ->count();
115
116
        $availableInvites = $user->invites - $activeInvitations;
117
118
        $this->viewData['invite_mode'] = true;
119
        $this->viewData['user_roles'] = config('nntmux.user_roles', []);
120
        $this->viewData['user_invites_left'] = $availableInvites;
121
        $this->viewData['user_invites_total'] = $user->invites;
122
        $this->viewData['user_invites_pending'] = $activeInvitations;
123
        $this->viewData['can_send_invites'] = $availableInvites > 0;
124
125
        return view('invitations.create', $this->viewData);
126
    }
127
128
    /**
129
     * Store a newly created invitation
130
     */
131
    public function store(Request $request): RedirectResponse
132
    {
133
        if ((int) Settings::settingValue('registerstatus') !== Settings::REGISTER_STATUS_INVITE) {
134
            return redirect()->route('invitations.index')->with('error', 'Invitations are currently disabled.');
135
        }
136
137
        $request->validate([
138
            'email' => 'required|email|unique:users,email',
139
            'expiry_days' => 'sometimes|integer|min:1|max:30',
140
            'role' => 'sometimes|integer|in:'.implode(',', array_keys(config('nntmux.user_roles', []))),
141
        ]);
142
143
        try {
144
            $expiryDays = $request->get('expiry_days', Invitation::DEFAULT_INVITE_EXPIRY_DAYS);
145
            $metadata = [];
146
147
            if ($request->has('role')) {
148
                $metadata['role'] = $request->get('role');
149
            }
150
151
            $invitation = $this->invitationService->createAndSendInvitation(
0 ignored issues
show
The assignment to $invitation is dead and can be removed.
Loading history...
152
                $request->email,
153
                auth()->id(),
154
                $expiryDays,
155
                $metadata
156
            );
157
158
            return redirect()->route('invitations.index')
159
                ->with('success', 'Invitation sent successfully to '.$request->email);
160
161
        } catch (\Exception $e) {
162
            return redirect()->back()
163
                ->withInput()
164
                ->with('error', $e->getMessage());
165
        }
166
    }
167
168
    /**
169
     * Display the specified invitation
170
     */
171
    public function show(string $token): View
172
    {
173
174
        $preview = $this->invitationService->getInvitationPreview($token);
175
176
        // Convert timestamps
177
        if ($preview && isset($preview['expires_at'])) {
178
            $preview['expires_at'] = strtotime($preview['expires_at']);
179
        }
180
181
        // Add role name if role is set
182
        if ($preview && isset($preview['metadata']['role'])) {
183
            $roles = config('nntmux.user_roles', []);
184
            $preview['role_name'] = $roles[$preview['metadata']['role']] ?? 'Default';
185
        }
186
187
        $this->viewData['preview'] = $preview;
188
        $this->viewData['token'] = $token;
189
190
        // Set meta information
191
        $this->viewData['meta_title'] = $preview ? 'Invitation to Join' : 'Invalid Invitation';
192
        $this->viewData['meta_keywords'] = 'invitation,join,register,signup';
193
        $this->viewData['meta_description'] = $preview ? 'You have been invited to join our community' : 'This invitation link is invalid or expired';
194
195
        return view('invitations.show', $this->viewData);
196
    }
197
198
    /**
199
     * Resend an invitation
200
     */
201
    public function resend(int $id): RedirectResponse
202
    {
203
        if ((int) Settings::settingValue('registerstatus') !== Settings::REGISTER_STATUS_INVITE) {
204
            return redirect()->route('invitations.index')->with('error', 'Invitations are currently disabled.');
205
        }
206
207
        try {
208
            $invitation = Invitation::findOrFail($id);
209
210
            // Check if user owns this invitation
211
            if ($invitation->invited_by !== auth()->id()) {
0 ignored issues
show
The property invited_by does not seem to exist on Illuminate\Database\Eloq...gHasThroughRelationship.
Loading history...
212
                abort(403, 'Unauthorized');
213
            }
214
215
            $this->invitationService->resendInvitation($id);
216
217
            return redirect()->route('invitations.index')
218
                ->with('success', 'Invitation resent successfully');
219
220
        } catch (\Exception $e) {
221
            return redirect()->back()
222
                ->with('error', $e->getMessage());
223
        }
224
    }
225
226
    /**
227
     * Cancel an invitation
228
     */
229
    public function destroy(int $id): RedirectResponse
230
    {
231
        if ((int) Settings::settingValue('registerstatus') !== Settings::REGISTER_STATUS_INVITE) {
232
            return redirect()->route('invitations.index')->with('error', 'Invitations are currently disabled.');
233
        }
234
235
        try {
236
            $invitation = Invitation::findOrFail($id);
237
238
            // Check if user owns this invitation
239
            if ($invitation->invited_by !== auth()->id()) {
0 ignored issues
show
The property invited_by does not seem to exist on Illuminate\Database\Eloq...gHasThroughRelationship.
Loading history...
240
                abort(403, 'Unauthorized');
241
            }
242
243
            $this->invitationService->cancelInvitation($id);
244
245
            return redirect()->route('invitations.index')
246
                ->with('success', 'Invitation cancelled successfully');
247
248
        } catch (\Exception $e) {
249
            return redirect()->back()
250
                ->with('error', $e->getMessage());
251
        }
252
    }
253
254
    /**
255
     * Get invitation statistics (API endpoint)
256
     */
257
    public function stats(): JsonResponse
258
    {
259
        if ((int) Settings::settingValue('registerstatus') !== Settings::REGISTER_STATUS_INVITE) {
260
            return response()->json(['message' => 'Invitations are disabled'], 404);
261
        }
262
263
        $stats = $this->invitationService->getUserInvitationStats(auth()->id());
264
265
        return response()->json($stats);
266
    }
267
268
    /**
269
     * Clean up expired invitations (admin only)
270
     */
271
    public function cleanup(): JsonResponse
272
    {
273
        // Check if user is admin
274
        if (! auth()->user()->hasRole('admin')) {
275
            abort(403, 'Unauthorized');
276
        }
277
278
        $cleanedCount = $this->invitationService->cleanupExpiredInvitations();
279
280
        return response()->json([
281
            'message' => "Cleaned up {$cleanedCount} expired invitations",
282
            'count' => $cleanedCount,
283
        ]);
284
    }
285
}
286