Completed
Push — dev ( a18286...cd5777 )
by Darko
06:03
created

ApiController   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 300
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 61
eloc 219
c 1
b 0
f 1
dl 0
loc 300
rs 3.52

1 Method

Rating   Name   Duplication   Size   Complexity  
F api() 0 294 61

How to fix   Complexity   

Complex Class

Complex classes like ApiController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ApiController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace App\Http\Controllers\Api;
4
5
use App\Models\User;
6
use App\Models\Release;
7
use Blacklight\http\API;
8
use Blacklight\Releases;
9
use App\Models\ReleaseNfo;
10
use App\Models\UserRequest;
11
use App\Models\UserDownload;
12
use Illuminate\Http\Request;
13
use Illuminate\Support\Carbon;
14
use App\Events\UserAccessedApi;
15
use Blacklight\utility\Utility;
16
use App\Http\Controllers\BasePageController;
17
18
class ApiController extends BasePageController
19
{
20
    /**
21
     * @param \Illuminate\Http\Request $request
22
     * @throws \Throwable
23
     */
24
    public function api(Request $request)
25
    {
26
        // API functions.
27
        $function = 's';
28
        if ($request->has('t')) {
29
            switch ($request->input('t')) {
30
               case 'd':
31
               case 'details':
32
                   $function = 'd';
33
                   break;
34
               case 'g':
35
               case 'get':
36
                   $function = 'g';
37
                   break;
38
               case 's':
39
               case 'search':
40
                   $function = 's';
41
                   break;
42
               case 'c':
43
               case 'caps':
44
                   $function = 'c';
45
                   break;
46
               case 'tv':
47
               case 'tvsearch':
48
                   $function = 'tv';
49
                   break;
50
               case 'm':
51
               case 'movie':
52
                   $function = 'm';
53
                   break;
54
               case 'gn':
55
               case 'n':
56
               case 'nfo':
57
               case 'info':
58
                   $function = 'n';
59
                   break;
60
               default:
61
                   Utility::showApiError(202, 'No such function ('.$request->input('t').')');
62
           }
63
        } else {
64
            Utility::showApiError(200, 'Missing parameter (t)');
65
        }
66
67
        $uid = $apiKey = $oldestGrabTime = $apiOldestTime = '';
68
        $res = $catExclusions = [];
69
        $maxRequests = $apiRequests = $maxDownloads = $grabs = 0;
70
71
        // Page is accessible only by the apikey
72
73
        if ($function !== 'c' && $function !== 'r') {
74
            if (! $request->has('apikey') || ($request->has('apikey') && empty($request->input('apikey')))) {
75
                Utility::showApiError(200, 'Missing parameter (apikey)');
76
            } else {
77
                $apiKey = $request->input('apikey');
78
                $res = User::getByRssToken($apiKey);
79
                if ($res === null) {
80
                    Utility::showApiError(100, 'Incorrect user credentials (wrong API key)');
81
                }
82
            }
83
84
            if ($res->hasRole('Disabled')) {
85
                Utility::showApiError(101);
86
            }
87
88
            $uid = $res['id'];
89
            $catExclusions = User::getCategoryExclusionForApi($request);
90
            $maxRequests = $res->role->apirequests;
0 ignored issues
show
Bug introduced by
The property apirequests does not seem to exist on Spatie\Permission\Models\Role. 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...
91
            $maxDownloads = $res->role->downloadrequests;
0 ignored issues
show
Bug introduced by
The property downloadrequests does not seem to exist on Spatie\Permission\Models\Role. 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...
92
            $time = UserRequest::whereUsersId($uid)->min('timestamp');
93
            $apiOldestTime = $time !== null ? Carbon::createFromTimeString($time)->toRfc822String() : '';
0 ignored issues
show
Bug introduced by
It seems like $time can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $time of Carbon\Carbon::createFromTimeString() 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

93
            $apiOldestTime = $time !== null ? Carbon::createFromTimeString(/** @scrutinizer ignore-type */ $time)->toRfc822String() : '';
Loading history...
94
            $grabTime = UserDownload::whereUsersId($uid)->min('timestamp');
95
            $oldestGrabTime = $grabTime !== null ? Carbon::createFromTimeString($grabTime)->toRfc822String() : '';
0 ignored issues
show
Bug introduced by
It seems like $grabTime can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $time of Carbon\Carbon::createFromTimeString() 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

95
            $oldestGrabTime = $grabTime !== null ? Carbon::createFromTimeString(/** @scrutinizer ignore-type */ $grabTime)->toRfc822String() : '';
Loading history...
96
        }
97
98
        // Record user access to the api, if its been called by a user (i.e. capabilities request do not require a user to be logged in or key provided).
99
        if ($uid !== '') {
100
            event(new UserAccessedApi($res));
101
            $apiRequests = UserRequest::getApiRequests($uid);
102
            $grabs = UserDownload::getDownloadRequests($uid);
103
            if ($apiRequests > $maxRequests) {
104
                Utility::showApiError(500, 'Request limit reached ('.$apiRequests.'/'.$maxRequests.')');
105
            }
106
        }
107
108
        $releases = new Releases();
109
        $api = new API(['Settings' => $this->settings, 'Request' => $request]);
110
111
        // Set Query Parameters based on Request objects
112
        $outputXML = ! ($request->has('o') && $request->input('o') === 'json');
113
        $minSize = $request->has('minsize') && $request->input('minsize') > 0 ? $request->input('minsize') : 0;
114
        $offset = $api->offset();
115
116
        // Set API Parameters based on Request objects
117
        $params['extended'] = $request->has('extended') && (int) $request->input('extended') === 1 ? '1' : '0';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
118
        $params['del'] = $request->has('del') && (int) $request->input('del') === 1 ? '1' : '0';
119
        $params['uid'] = $uid;
120
        $params['token'] = $apiKey;
121
        $params['apilimit'] = $maxRequests;
122
        $params['requests'] = $apiRequests;
123
        $params['downloadlimit'] = $maxDownloads;
124
        $params['grabs'] = $grabs;
125
        $params['oldestapi'] = $apiOldestTime;
126
        $params['oldestgrab'] = $oldestGrabTime;
127
128
        switch ($function) {
129
           // Search releases.
130
           case 's':
131
               $api->verifyEmptyParameter('q');
132
               $maxAge = $api->maxAge();
133
               $groupName = $api->group();
134
               UserRequest::addApiRequest($apiKey, $request->getRequestUri());
135
               $categoryID = $api->categoryID();
136
               $limit = $api->limit();
137
               $searchArr = [
138
                   'searchname' => $request->input('q') ?? -1,
139
                   'name' => -1,
140
                   'fromname' => -1,
141
                   'filename' => -1,
142
               ];
143
144
               if ($request->has('q')) {
145
                   $relData = $releases->search(
146
                       $searchArr,
147
                       $groupName,
148
                       -1,
149
                       -1,
150
                       -1,
151
                       -1,
152
                       $offset,
153
                       $limit,
154
                       '',
155
                       $maxAge,
156
                       $catExclusions,
157
                       'basic',
158
                       $categoryID,
159
                       $minSize
160
                   );
161
               } else {
162
                   $relData = $releases->getBrowseRange(
163
                       1,
164
                       $categoryID,
165
                       $offset,
166
                       $limit,
167
                       '',
168
                       $maxAge,
169
                       $catExclusions,
170
                       $groupName,
0 ignored issues
show
Bug introduced by
It seems like $groupName can also be of type string and true; however, parameter $groupName of Blacklight\Releases::getBrowseRange() does only seem to accept integer, 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

170
                       /** @scrutinizer ignore-type */ $groupName,
Loading history...
171
                       $minSize
172
                   );
173
               }
174
               $api->output($relData, $params, $outputXML, $offset, 'api');
175
               break;
176
           // Search tv releases.
177
           case 'tv':
178
               $api->verifyEmptyParameter('q');
179
               $api->verifyEmptyParameter('vid');
180
               $api->verifyEmptyParameter('tvdbid');
181
               $api->verifyEmptyParameter('traktid');
182
               $api->verifyEmptyParameter('rid');
183
               $api->verifyEmptyParameter('tvmazeid');
184
               $api->verifyEmptyParameter('imdbid');
185
               $api->verifyEmptyParameter('tmdbid');
186
               $api->verifyEmptyParameter('season');
187
               $api->verifyEmptyParameter('ep');
188
               $maxAge = $api->maxAge();
189
               UserRequest::addApiRequest($apiKey, $request->getRequestUri());
190
191
               $siteIdArr = [
192
                   'id'     => $request->input('vid') ?? '0',
193
                   'tvdb'   => $request->input('tvdbid') ?? '0',
194
                   'trakt'  => $request->input('traktid') ?? '0',
195
                   'tvrage' => $request->input('rid') ?? '0',
196
                   'tvmaze' => $request->input('tvmazeid') ?? '0',
197
                   'imdb'   => $request->input('imdbid') ?? '0',
198
                   'tmdb'   => $request->input('tmdbid') ?? '0',
199
               ];
200
201
               // Process season only queries or Season and Episode/Airdate queries
202
203
               $series = $request->input('season') ?? '';
204
               $episode = $request->input('ep') ?? '';
205
206
               if (preg_match('#^(19|20)\d{2}$#', $series, $year) && strpos($episode, '/') !== false) {
207
                   $airdate = str_replace('/', '-', $year[0].'-'.$episode);
208
               }
209
210
               $relData = $releases->tvSearch(
211
                   $siteIdArr,
212
                   $series,
213
                   $episode,
214
                   $airdate ?? '',
215
                   $api->offset(),
216
                   $api->limit(),
217
                   $request->input('q') ?? '',
218
                   $api->categoryID(),
219
                   $maxAge,
220
                   $minSize,
221
                   $catExclusions
222
               );
223
224
               $api->addLanguage($relData);
0 ignored issues
show
Bug introduced by
It seems like $relData can also be of type Illuminate\Database\Eloquent\Collection; however, parameter $releases of Blacklight\http\API::addLanguage() does only seem to accept array, 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

224
               $api->addLanguage(/** @scrutinizer ignore-type */ $relData);
Loading history...
225
               $api->output($relData, $params, $outputXML, $offset, 'api');
226
               break;
227
228
           // Search movie releases.
229
           case 'm':
230
               $api->verifyEmptyParameter('q');
231
               $api->verifyEmptyParameter('imdbid');
232
               $maxAge = $api->maxAge();
233
               UserRequest::addApiRequest($apiKey, $request->getRequestUri());
234
235
               $imdbId = $request->has('imdbid') && ! empty($request->input('imdbid')) ? $request->input('imdbid') : -1;
236
               $tmdbId = $request->has('tmdbid') && ! empty($request->input('tmdbid')) ? $request->input('tmdbid') : -1;
237
               $traktId = $request->has('traktid') && ! empty($request->input('traktid')) ? $request->input('traktid') : -1;
238
239
               $relData = $releases->moviesSearch(
240
                   $imdbId,
241
                   $tmdbId,
242
                   $traktId,
243
                   $api->offset(),
244
                   $api->limit(),
245
                   $request->input('q') ?? '',
246
                   $api->categoryID(),
247
                   $maxAge,
248
                   $minSize,
249
                   $catExclusions
250
               );
251
252
               $api->addCoverURL(
253
                   $relData,
254
                   function ($release) {
255
                       return Utility::getCoverURL(['type' => 'movies', 'id' => $release->imdbid]);
256
                   }
257
               );
258
259
               $api->addLanguage($relData);
260
               $api->output($relData, $params, $outputXML, $offset, 'api');
261
               break;
262
263
           // Get NZB.
264
           case 'g':
265
               $api->verifyEmptyParameter('g');
266
               UserRequest::addApiRequest($apiKey, $request->getRequestUri());
267
               $relData = Release::checkGuidForApi($request->input('id'));
268
               if ($relData) {
269
                   return redirect(WWW_TOP.'/getnzb?r='.$apiKey.'&id='.$request->input('id').(($request->has('del') && $request->input('del') === '1') ? '&del=1' : ''));
0 ignored issues
show
Bug introduced by
The constant App\Http\Controllers\Api\WWW_TOP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
270
               }
271
272
               Utility::showApiError(300, 'No such item (the guid you provided has no release in our database)');
273
               break;
274
275
           // Get individual NZB details.
276
           case 'd':
277
               if (! $request->has('id')) {
278
                   Utility::showApiError(200, 'Missing parameter (guid is required for single release details)');
279
               }
280
281
               UserRequest::addApiRequest($apiKey, $request->getRequestUri());
282
               $data = Release::getByGuid($request->input('id'));
283
284
               $api->output($data, $params, $outputXML, $offset, 'api');
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type App\Models\Release and Illuminate\Database\Eloquent\Model; however, parameter $data of Blacklight\http\Capabilities::output() does only seem to accept Illuminate\Database\Eloquent\Collection|array, 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

284
               $api->output(/** @scrutinizer ignore-type */ $data, $params, $outputXML, $offset, 'api');
Loading history...
285
               break;
286
287
           // Get an NFO file for an individual release.
288
           case 'n':
289
               if (! $request->has('id')) {
290
                   Utility::showApiError(200, 'Missing parameter (id is required for retrieving an NFO)');
291
               }
292
293
               UserRequest::addApiRequest($apiKey, $request->getRequestUri());
294
               $rel = Release::query()->where('guid', $request->input('id'))->first(['id', 'searchname']);
295
               $data = ReleaseNfo::getReleaseNfo($rel['id']);
296
297
               if ($rel !== null) {
298
                   if ($data !== null) {
299
                       if ($request->has('o') && $request->input('o') === 'file') {
300
                           return response()->streamDownload(function () use ($data) {
301
                               echo $data['nfo'];
302
                           }, $rel['searchname'].'.nfo', ['Content-type:' => 'application/octet-stream']);
303
                       }
304
305
                       echo nl2br(Utility::cp437toUTF($data['nfo']));
306
                   } else {
307
                       Utility::showApiError(300, 'Release does not have an NFO file associated.');
308
                   }
309
               } else {
310
                   Utility::showApiError(300, 'Release does not exist.');
311
               }
312
               break;
313
314
           // Capabilities request.
315
           case 'c':
316
               $api->output([], $params, $outputXML, $offset, 'caps');
317
               break;
318
       }
319
    }
320
}
321