MagicLink::getValidMagicLinkByToken()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 14
dl 0
loc 21
rs 9.7998
c 3
b 0
f 1
cc 2
nc 2
nop 1
1
<?php
2
3
namespace MagicLink;
4
5
use Carbon\Carbon;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\QueryException;
8
use Illuminate\Support\Facades\Event;
9
use Illuminate\Support\Facades\Hash;
10
use Illuminate\Support\Str;
11
use MagicLink\Actions\ActionAbstract;
12
use MagicLink\Events\MagicLinkWasCreated;
13
use MagicLink\Events\MagicLinkWasDeleted;
14
use MagicLink\Events\MagicLinkWasVisited;
15
16
/**
17
 * @property string $token
18
 * @property Carbon|null $available_at
19
 * @property int|null $max_visits
20
 * @property int|null $num_visits
21
 * @property \MagicLink\Actions\ActionAbstract $action
22
 * @property-read string $url
23
 * @property int|string $access_code
24
 */
25
class MagicLink extends Model
26
{
27
    use AccessCode;
28
29
    public function getAccessCode()
30
    {
31
        return $this->access_code ?? null;
32
    }
33
34
    public function getMagikLinkId()
35
    {
36
        return $this->getKey();
37
    }
38
39
    public $incrementing = false;
40
41
    protected $keyType = 'string';
42
43
    protected static function boot()
44
    {
45
        parent::boot();
46
47
        static::creating(function ($model) {
48
            $model->id = Str::uuid();
49
        });
50
    }
51
52
    protected static function getTokenLength()
53
    {
54
        return config('magiclink.token.length', 64) <= 255
55
            ? config('magiclink.token.length', 64)
56
            : 255;
57
    }
58
59
    public function getActionAttribute($value)
60
    {
61
        if ($this->getConnection()->getDriverName() === 'pgsql') {
62
            return unserialize(base64_decode($value));
63
        }
64
65
        return unserialize($value);
66
    }
67
68
    public function setActionAttribute($value)
69
    {
70
        $this->attributes['action'] = $this->getConnection()->getDriverName() === 'pgsql'
71
                                        ? base64_encode(serialize($value))
72
                                        : serialize($value);
73
    }
74
75
    public function baseUrl(?string $baseUrl): self
76
    {
77
        $this->attributes['base_url'] = rtrim($baseUrl, '/') . '/';
0 ignored issues
show
Bug introduced by
It seems like $baseUrl can also be of type null; however, parameter $string of rtrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

77
        $this->attributes['base_url'] = rtrim(/** @scrutinizer ignore-type */ $baseUrl, '/') . '/';
Loading history...
78
79
        return $this;
80
    }
81
82
    public function getUrlAttribute(): string
83
    {
84
        $baseUrl = rtrim($this->attributes['base_url'] ?? '', '/') . '/'; // Use the stored base_url or an empty string
85
86
        return url(sprintf(
87
            '%s%s/%s%s%s',
88
            $baseUrl,
89
            config('magiclink.url.validate_path', 'magiclink'),
90
            $this->id,
91
            urlencode(':'),
92
            $this->token
93
        ));
94
    }
95
96
    /**
97
     * Create MagicLink.
98
     *
99
     * @return self
100
     */
101
    public static function create(ActionAbstract $action, ?int $lifetime = 4320, ?int $numMaxVisits = null)
102
    {
103
        static::deleteMagicLinkExpired();
104
105
        $magiclink = new static();
106
107
        $magiclink->token = Str::random(static::getTokenLength());
108
        $magiclink->available_at = $lifetime
109
                                    ? Carbon::now()->addMinutes($lifetime)
110
                                    : null;
111
        $magiclink->max_visits = $numMaxVisits;
112
        $magiclink->action = $action;
113
114
        $magiclink->save();
115
116
        $magiclink->action = $action->setMagicLinkId($magiclink->id);
117
118
        $magiclink->save();
119
120
        Event::dispatch(new MagicLinkWasCreated($magiclink));
121
122
        return $magiclink;
123
    }
124
125
    /**
126
     * Protect the Action with an access code.
127
     */
128
    public function protectWithAccessCode(string $accessCode): self
129
    {
130
        $this->access_code = Hash::make($accessCode);
131
132
        $this->save();
133
134
        return $this;
135
    }
136
137
    /**
138
     * Execute Action.
139
     *
140
     * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
141
     */
142
    public function run()
143
    {
144
        return $this->action->run();
145
    }
146
147
    /**
148
     * Call when magiclink has been visited.
149
     *
150
     * @return void
151
     */
152
    public function visited()
153
    {
154
        try {
155
            $this->increment('num_visits');
156
        } catch (QueryException $e) {
157
            // catch exceptino if fails to increment num_visits
158
        }
159
160
        Event::dispatch(new MagicLinkWasVisited($this));
161
    }
162
163
    /**
164
     * Get valid MagicLink by token.
165
     *
166
     * @param  string  $token
167
     * @return \MagicLink\MagicLink|null
168
     */
169
    public static function getValidMagicLinkByToken($token)
170
    {
171
        [$tokenId, $tokenSecret] = explode(':', "{$token}:");
172
173
        if (empty($tokenSecret)) {
174
            return null;
175
        }
176
177
        return static::where('id', $tokenId)
178
                    ->where('token', $tokenSecret)
179
                    ->where(function ($query) {
180
                        $query
181
                            ->whereNull('available_at')
182
                            ->orWhere('available_at', '>=', Carbon::now());
183
                    })
184
                    ->where(function ($query) {
185
                        $query
186
                            ->whereNull('max_visits')
187
                            ->orWhereRaw('max_visits > num_visits');
188
                    })
189
                    ->first();
190
    }
191
192
    /**
193
     * Get MagicLink by token.
194
     *
195
     * @param  string  $token
196
     * @return \MagicLink\MagicLink|null
197
     */
198
    public static function getMagicLinkByToken($token)
199
    {
200
        [$tokenId, $tokenSecret] = explode(':', "{$token}:");
201
202
        if (empty($tokenSecret)) {
203
            return null;
204
        }
205
206
        return static::where('id', $tokenId)
207
                    ->where('token', $tokenSecret)
208
                    ->first();
209
    }
210
211
    /**
212
     * Delete MagicLink was expired.
213
     *
214
     * @return void
215
     */
216
    public static function deleteMagicLinkExpired()
217
    {
218
        $query = MagicLink::where(function ($query) {
219
            $query
220
                ->where('available_at', '<', Carbon::now())
221
                ->orWhere(function ($query) {
222
                    $query
223
                        ->whereNotNull('max_visits')
224
                        ->whereRaw('max_visits <= num_visits');
225
                });
226
        });
227
228
        if (config('magiclink.delete_massive', true)) {
229
            $query->delete();
230
231
            return;
232
        }
233
234
235
        $query->get()->each(function (MagicLink $magiclink) {
236
            $magiclink->delete();
237
238
            event(new MagicLinkWasDeleted($magiclink));
239
        });
240
    }
241
242
    /**
243
     * Delete all MagicLink.
244
     *
245
     * @return void
246
     */
247
    public static function deleteAllMagicLink()
248
    {
249
        static::truncate();
250
    }
251
}
252