Passed
Pull Request — master (#39)
by Cesar
10:31 queued 04:08
created

MagicLink::create()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 18
rs 9.9
cc 2
nc 2
nop 3
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\ActionInterface;
12
use MagicLink\Events\MagicLinkWasCreated;
13
use MagicLink\Events\MagicLinkWasVisited;
14
15
class MagicLink extends Model
16
{
17
    public $incrementing = false;
18
19
    protected $keyType = 'string';
20
21
    protected static function boot()
22
    {
23
        parent::boot();
24
25
        static::creating(function ($model) {
26
            $model->id = Str::uuid();
27
        });
28
    }
29
30
    protected static function getTokenLength()
31
    {
32
        return config('magiclink.token.length', 64) <= 255
33
            ? config('magiclink.token.length', 64)
34
            : 255;
35
    }
36
37
    public function getActionAttribute($value)
38
    {
39
        if ($this->getConnection()->getDriverName() === 'pgsql') {
40
            return unserialize(base64_decode($value));
41
        }
42
43
        return unserialize($value);
44
    }
45
46
    public function setActionAttribute($value)
47
    {
48
        $this->attributes['action'] = $this->getConnection()->getDriverName() === 'pgsql'
49
                                        ? base64_encode(serialize($value))
50
                                        : serialize($value);
51
    }
52
53
    public function getUrlAttribute()
54
    {
55
        return url(sprintf(
56
            '%s/%s:%s',
57
            config('magiclink.url.validate_path', 'magiclink'),
58
            $this->id,
59
            $this->token
60
        ));
61
    }
62
63
    /**
64
     * Create makiglink.
65
     *
66
     * @param ActionInterface $action
67
     * @param int|null $lifetime
68
     * @param int|null $numMaxVisits
69
     * @return self
70
     */
71
    public static function create(ActionInterface $action, ?int $lifetime = 4320, ?int $numMaxVisits = null)
72
    {
73
        self::deleteMagicLinkExpired();
74
75
        $magiclink = new static();
76
77
        $magiclink->token = Str::random(self::getTokenLength());
0 ignored issues
show
Bug introduced by
The property token does not seem to exist on MagicLink\MagicLink. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
78
        $magiclink->available_at = $lifetime
0 ignored issues
show
Bug introduced by
The property available_at does not seem to exist on MagicLink\MagicLink. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
79
                                    ? Carbon::now()->addMinute($lifetime)
0 ignored issues
show
Unused Code introduced by
The call to Carbon\Carbon::addMinute() has too many arguments starting with $lifetime. ( Ignorable by Annotation )

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

79
                                    ? Carbon::now()->/** @scrutinizer ignore-call */ addMinute($lifetime)

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
80
                                    : null;
81
        $magiclink->max_visits = $numMaxVisits;
0 ignored issues
show
Bug introduced by
The property max_visits does not seem to exist on MagicLink\MagicLink. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
82
        $magiclink->action = $action;
0 ignored issues
show
Bug introduced by
The property action does not seem to exist on MagicLink\MagicLink. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
83
84
        $magiclink->save();
85
86
        Event::dispatch(new MagicLinkWasCreated($magiclink));
87
88
        return $magiclink;
89
    }
90
91
    /**
92
     * Protect the Action with an access code.
93
     *
94
     * @param string $accessCode
95
     * @return self
96
     */
97
    public function protectWithAccessCode(string $accessCode): self
98
    {
99
        $this->access_code = Hash::make($accessCode);
100
101
        $this->save();
102
103
        return $this;
104
    }
105
106
    /**
107
     * Check if access code is right.
108
     *
109
     * @param string|null $accessCode
110
     * @return bool
111
     */
112
    public function checkAccessCode(?string $accessCode): bool
113
    {
114
        if ($accessCode === null) {
115
            return false;
116
        }
117
118
        return Hash::check($accessCode, $this->access_code);
119
    }
120
121
    /**
122
     * The action was protected with an access code.
123
     *
124
     * @return bool
125
     */
126
    public function protectedWithAcessCode(): bool
127
    {
128
        return ! is_null($this->access_code ?? null);
129
    }
130
131
    /**
132
     * Execute Action.
133
     *
134
     * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
135
     */
136
    public function run()
137
    {
138
        return $this->action->run();
139
    }
140
141
    /**
142
     * Call when magiclink has been visited.
143
     *
144
     * @return void
145
     */
146
    public function visited()
147
    {
148
        try {
149
            $this->increment('num_visits');
150
        } catch (QueryException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
151
        }
152
153
        Event::dispatch(new MagicLinkWasVisited($this));
154
    }
155
156
    /**
157
     * Get valid MagicLink by token.
158
     *
159
     * @param string $token
160
     * @return null|\MagicLink\MagicLink
161
     */
162
    public static function getValidMagicLinkByToken($token)
163
    {
164
        [$tokenId, $tokenSecret] = explode(':', "{$token}:");
165
166
        if (empty($tokenSecret)) {
167
            return;
168
        }
169
170
        return self::where('id', $tokenId)
171
                    ->where('token', $tokenSecret)
172
                    ->where(function ($query) {
173
                        $query
174
                            ->whereNull('available_at')
175
                            ->orWhere('available_at', '>=', Carbon::now());
176
                    })
177
                    ->where(function ($query) {
178
                        $query
179
                            ->whereNull('max_visits')
180
                            ->orWhereRaw('max_visits > num_visits');
181
                    })
182
                    ->first();
183
    }
184
185
    /**
186
     * Get MagicLink by token.
187
     *
188
     * @param string $token
189
     * @return null|\MagicLink\MagicLink
190
     */
191
    public static function getMagicLinkByToken($token)
192
    {
193
        [$tokenId, $tokenSecret] = explode(':', "{$token}:");
194
195
        if (empty($tokenSecret)) {
196
            return;
197
        }
198
199
        return self::where('id', $tokenId)
200
                    ->where('token', $tokenSecret)
201
                    ->first();
202
    }
203
204
    /**
205
     * Delete magiclink was expired.
206
     *
207
     * @return void
208
     */
209
    public static function deleteMagicLinkExpired()
210
    {
211
        self::where(function ($query) {
212
            $query
213
                ->where('available_at', '<', Carbon::now())
214
                ->orWhere(function ($query) {
215
                    $query
216
                        ->whereNotNull('max_visits')
217
                        ->whereRaw('max_visits <= num_visits');
218
                });
219
        })
220
        ->delete();
221
    }
222
223
    /**
224
     * Delete all MagicLink.
225
     *
226
     * @return void
227
     */
228
    public static function deleteAllMagicLink()
229
    {
230
        self::truncate();
231
    }
232
}
233