Passed
Push — master ( 6fbdf8...6e22c4 )
by Cesar
13:47
created

MagicLink::baseUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
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\MagicLinkWasVisited;
14
15
/**
16
 * @property string $token
17
 * @property Carbon|null $available_at
18
 * @property int|null $max_visits
19
 * @property int|null $num_visits
20
 * @property \MagicLink\Actions\ActionAbstract $action
21
 * @property-read string $url
22
 * @property int|string $access_code
23
 */
24
class MagicLink extends Model
25
{
26
    use AccessCode;
27
28
    public function getAccessCode()
29
    {
30
        return $this->access_code ?? null;
31
    }
32
33
    public function getMagikLinkId()
34
    {
35
        return $this->getKey();
36
    }
37
38
    public $incrementing = false;
39
40
    protected $keyType = 'string';
41
42
    protected static function boot()
43
    {
44
        parent::boot();
45
46
        static::creating(function ($model) {
47
            $model->id = Str::uuid();
48
        });
49
    }
50
51
    protected static function getTokenLength()
52
    {
53
        return config('magiclink.token.length', 64) <= 255
54
            ? config('magiclink.token.length', 64)
55
            : 255;
56
    }
57
58
    public function getActionAttribute($value)
59
    {
60
        if ($this->getConnection()->getDriverName() === 'pgsql') {
61
            return unserialize(base64_decode($value));
62
        }
63
64
        return unserialize($value);
65
    }
66
67
    public function setActionAttribute($value)
68
    {
69
        $this->attributes['action'] = $this->getConnection()->getDriverName() === 'pgsql'
70
                                        ? base64_encode(serialize($value))
71
                                        : serialize($value);
72
    }
73
74
    public function baseUrl(?string $baseUrl): self
75
    {
76
        $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

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