Passed
Push — master ( e84f2b...628701 )
by Paul
05:19
created

UrlService::validateUrl()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
cc 2
eloc 7
c 3
b 0
f 1
nc 2
nop 1
dl 0
loc 11
rs 10
1
<?php
2
3
namespace Devpri\Tinre\Services;
4
5
use Devpri\Tinre\Models\Url;
6
use Exception;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Http\Request;
9
use Illuminate\Support\Facades\DB;
10
use Illuminate\Support\Facades\Validator;
11
use Illuminate\Support\Str;
12
use Illuminate\Validation\ValidationException;
13
14
class UrlService
15
{
16
17
    public function index(Request $request)
18
    {
19
        $request->validate([
20
            'search' => ['nullable', 'string'],
21
            'date' => ['nullable', 'array'],
22
            'limit' => ['nullable', 'number', 'min:1', 'max:100'],
23
            'active' => ['nullable', 'boolean'],
24
            'sort_by' => ['nullable', 'in:created_at,updated_at,clicks'],
25
            'sort_direction' => ['nullable', 'in:asc,desc']
26
        ]);
27
        
28
        $user = $request->user();
29
        $search = $request->search;
30
        $date = $request->date;
31
        $limit = $request->limit ?? 30;
32
        $active =$request->active;
33
        $sortBy = $request->sort_by ?? 'created_at';
34
        $sortDirection = $request->sort_direction ?? 'desc';
35
                
36
        $query = Url::query();
37
38
        if ($user->cant('viewAny', Url::class)) {
39
            $query->where('user_id', $user->id);
40
        }
41
42
        if ($search) {
43
            $query->where(function (Builder $query) use ($search) {
44
                $query->where('path', 'LIKE', "%{$search}%")
45
                    ->orWhere('long_url', 'LIKE', "%{$search}%")
46
                    ->orWhereHas('user', function ($query) use ($search) {
47
                        $query->where('name', 'LIKE', "%{$search}%")
48
                            ->orWhere('email', 'LIKE', "%{$search}%");
49
                    });
50
            });
51
        }
52
53
        if ($date) {
54
            $query->whereBetween('created_at', $date);
55
        }
56
        
57
        if(isset($active)) {
58
            $query->where('active', $active);
59
        }
60
        
61
        return $query->orderBy($sortBy, $sortDirection)->with('user')->paginate($limit);
62
    }
63
64
    public function create(Request $request): Url
65
    {
66
        $this->validateUrl($request->long_url);
67
68
        $user = $request->user();
69
70
        if ($user && $user->cant('create', Url::class)) {
71
            abort(401);
72
        }
73
        
74
        $url = new Url();
75
        $url->long_url = $request->long_url;
76
        $url->user_id = $user ? $user->id : null;
77
78
        if ($request->path && ($user || config('tinre.guest_form_custom_path'))) {
79
            $url->path = $request->path;
80
81
            $this->validatePath($url->path);
82
83
            $url->save();
84
85
            return $url;
86
        }
87
88
        $maxGenerationAttempts = 5;
89
90
        while ($maxGenerationAttempts-- > 0) {
91
            try {
92
                $url->path = strtolower(Str::random(config('tinre.default_path_length')));
93
94
                $this->validatePath($url->path);
95
96
                $url->save();
97
98
                return $url;
99
            } catch (Exception $e) {
100
                if ($maxGenerationAttempts === 0) {
101
                    throw $e;
102
                }
103
            }
104
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Devpri\Tinre\Models\Url. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
105
    }
106
107
    public function update(Request $request, $id)
108
    {
109
        $request->validate([
110
            'active' => ['required', 'boolean'],
111
        ]);
112
113
        $this->validateUrl($request->long_url);
114
        
115
        $url = Url::where('id', $id)->firstOrFail();
116
117
        if($url->path != $request->path) {
118
            $this->validatePath($request->path);
119
        }
120
        
121
        $user = $request->user();
122
123
        if ($user->cant('update', $url)) {
124
            abort(401);
125
        }
126
127
        $url->active = $request->active;
128
        $url->long_url = $request->long_url;
129
        $url->path = $request->path;
130
        $url->save();
131
132
        return $url;
133
    }
134
135
    protected function validatePath($path): void
136
    {
137
        Validator::make([
138
            'path' => $path
139
        ], [
140
            'path' => ['required', 'alpha_dash', 'min:'.config('tinre.min_path_length'), 'max:'.config('tinre.max_path_length')],
141
        ])->validate();
142
        
143
        $path = strtolower($path);
144
145
        $url = DB::table('urls')->whereRaw('lower(path) like (?)', ["%{$path}%"])->count();
146
147
        if ($url) {
148
            throw ValidationException::withMessages([
149
                'path' => [__('The path has already been taken.')],
150
            ]);
151
        }
152
153
        if (in_array($path, config('tinre.restricted_paths'))) {
154
            throw ValidationException::withMessages([
155
                'path' => [__('Restricted path.')],
156
            ]);
157
        }
158
    }
159
160
    protected function validateUrl($url): void
161
    {
162
        Validator::make([
163
            'long_url' => $url
164
        ], [
165
            'long_url' => ['required', 'url', 'active_url'],
166
        ])->validate();
167
        
168
        if (in_array(parse_url($url, PHP_URL_HOST), config('tinre.restricted_domains'))) {
169
            throw ValidationException::withMessages([
170
                'long_url' => [__('Restricted domain.')],
171
            ]);
172
        }
173
    }
174
}
175