Passed
Push — master ( ff8dbf...bffc83 )
by Cesar
03:50 queued 12s
created

MagicLink   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 202
Duplicated Lines 0 %

Importance

Changes 16
Bugs 5 Features 3
Metric Value
wmc 22
eloc 73
c 16
b 5
f 3
dl 0
loc 202
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getMagicLinkByToken() 0 11 2
A deleteMagicLinkExpired() 0 12 1
A deleteAllMagicLink() 0 3 1
A protectWithAccessCode() 0 7 1
A visited() 0 9 2
A getUrlAttribute() 0 7 1
A setActionAttribute() 0 5 2
A getMagikLinkId() 0 3 1
A getActionAttribute() 0 7 2
A getTokenLength() 0 5 2
A create() 0 22 2
A boot() 0 6 1
A getValidMagicLinkByToken() 0 21 2
A run() 0 3 1
A getAccessCode() 0 3 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 \MagicLink\Actions\ActionAbstract $action
20
 * @property-read string $url
21
 */
22
class MagicLink extends Model
23
{
24
    use AccessCode;
25
26
    public function getAccessCode()
27
    {
28
        return $this->access_code ?? null;
29
    }
30
31
    public function getMagikLinkId()
32
    {
33
        return $this->getKey();
34
    }
35
36
    public $incrementing = false;
37
38
    protected $keyType = 'string';
39
40
    protected static function boot()
41
    {
42
        parent::boot();
43
44
        static::creating(function ($model) {
45
            $model->id = Str::uuid();
46
        });
47
    }
48
49
    protected static function getTokenLength()
50
    {
51
        return config('magiclink.token.length', 64) <= 255
52
            ? config('magiclink.token.length', 64)
53
            : 255;
54
    }
55
56
    public function getActionAttribute($value)
57
    {
58
        if ($this->getConnection()->getDriverName() === 'pgsql') {
59
            return unserialize(base64_decode($value));
60
        }
61
62
        return unserialize($value);
63
    }
64
65
    public function setActionAttribute($value)
66
    {
67
        $this->attributes['action'] = $this->getConnection()->getDriverName() === 'pgsql'
68
                                        ? base64_encode(serialize($value))
69
                                        : serialize($value);
70
    }
71
72
    public function getUrlAttribute()
73
    {
74
        return url(sprintf(
75
            '%s/%s:%s',
76
            config('magiclink.url.validate_path', 'magiclink'),
77
            $this->id,
78
            $this->token
79
        ));
80
    }
81
82
    /**
83
     * Create makiglink.
84
     *
85
     * @return self
86
     */
87
    public static function create(ActionAbstract $action, ?int $lifetime = 4320, ?int $numMaxVisits = null)
88
    {
89
        self::deleteMagicLinkExpired();
90
91
        $magiclink = new static();
92
93
        $magiclink->token = Str::random(self::getTokenLength());
94
        $magiclink->available_at = $lifetime
95
                                    ? Carbon::now()->addMinutes($lifetime)
96
                                    : null;
97
        $magiclink->max_visits = $numMaxVisits;
98
        $magiclink->action = $action;
99
100
        $magiclink->save();
101
102
        $magiclink->action = $action->setMagicLinkId($magiclink->id);
103
104
        $magiclink->save();
105
106
        Event::dispatch(new MagicLinkWasCreated($magiclink));
107
108
        return $magiclink;
109
    }
110
111
    /**
112
     * Protect the Action with an access code.
113
     */
114
    public function protectWithAccessCode(string $accessCode): self
115
    {
116
        $this->access_code = Hash::make($accessCode);
117
118
        $this->save();
119
120
        return $this;
121
    }
122
123
    /**
124
     * Execute Action.
125
     *
126
     * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
127
     */
128
    public function run()
129
    {
130
        return $this->action->run();
131
    }
132
133
    /**
134
     * Call when magiclink has been visited.
135
     *
136
     * @return void
137
     */
138
    public function visited()
139
    {
140
        try {
141
            $this->increment('num_visits');
142
        } catch (QueryException $e) {
143
            // catch exceptino if fails to increment num_visits
144
        }
145
146
        Event::dispatch(new MagicLinkWasVisited($this));
147
    }
148
149
    /**
150
     * Get valid MagicLink by token.
151
     *
152
     * @param  string  $token
153
     * @return \MagicLink\MagicLink|null
154
     */
155
    public static function getValidMagicLinkByToken($token)
156
    {
157
        [$tokenId, $tokenSecret] = explode(':', "{$token}:");
158
159
        if (empty($tokenSecret)) {
160
            return;
161
        }
162
163
        return self::where('id', $tokenId)
164
                    ->where('token', $tokenSecret)
165
                    ->where(function ($query) {
166
                        $query
167
                            ->whereNull('available_at')
168
                            ->orWhere('available_at', '>=', Carbon::now());
169
                    })
170
                    ->where(function ($query) {
171
                        $query
172
                            ->whereNull('max_visits')
173
                            ->orWhereRaw('max_visits > num_visits');
174
                    })
175
                    ->first();
176
    }
177
178
    /**
179
     * Get MagicLink by token.
180
     *
181
     * @param  string  $token
182
     * @return \MagicLink\MagicLink|null
183
     */
184
    public static function getMagicLinkByToken($token)
185
    {
186
        [$tokenId, $tokenSecret] = explode(':', "{$token}:");
187
188
        if (empty($tokenSecret)) {
189
            return;
190
        }
191
192
        return self::where('id', $tokenId)
193
                    ->where('token', $tokenSecret)
194
                    ->first();
195
    }
196
197
    /**
198
     * Delete magiclink was expired.
199
     *
200
     * @return void
201
     */
202
    public static function deleteMagicLinkExpired()
203
    {
204
        self::where(function ($query) {
205
            $query
206
                ->where('available_at', '<', Carbon::now())
207
                ->orWhere(function ($query) {
208
                    $query
209
                        ->whereNotNull('max_visits')
210
                        ->whereRaw('max_visits <= num_visits');
211
                });
212
        })
213
        ->delete();
214
    }
215
216
    /**
217
     * Delete all MagicLink.
218
     *
219
     * @return void
220
     */
221
    public static function deleteAllMagicLink()
222
    {
223
        self::truncate();
224
    }
225
}
226