Passed
Push — develop ( be70d2...34d182 )
by Henry
03:13
created

MediaRequestHandler::handleCaptionRequest()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 17
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 33
ccs 0
cts 18
cp 0
crap 42
rs 9.0777
1
<?php
2
/**
3
 * This file is part of the Divergence package.
4
 *
5
 * (c) Henry Paradiz <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Divergence\Controllers;
12
13
use Exception;
14
use Divergence\Models\Media\Media;
15
use Divergence\Models\ActiveRecord;
16
use Divergence\Responders\Response;
17
use Divergence\Responders\JsonBuilder;
18
use Divergence\Responders\EmptyBuilder;
19
use Divergence\Responders\EmptyResponse;
20
use Divergence\Responders\MediaBuilder;
21
use Psr\Http\Message\ResponseInterface;
22
use Divergence\Responders\MediaResponse;
23
use GuzzleHttp\Psr7\ServerRequest;
24
use Psr\Http\Message\ServerRequestInterface;
25
26
class MediaRequestHandler extends RecordsRequestHandler
27
{
28
    // RecordRequestHandler configuration
29
    public static $recordClass = Media::class;
30
    public $accountLevelRead = false;
31
    public $accountLevelBrowse = 'User';
32
    public $accountLevelWrite = 'User';
33
    public $accountLevelAPI = false;
34
    public $browseLimit = 100;
35
    public $browseOrder = ['ID' => 'DESC'];
36
37
    // MediaRequestHandler configuration
38
    public $defaultPage = 'browse';
39
    public $defaultThumbnailWidth = 100;
40
    public $defaultThumbnailHeight = 100;
41
    public $uploadFileFieldName = 'mediaFile';
42
    public $responseMode = 'html';
43
44
    public static $inputStream = 'php://input'; // this is a setting so that unit tests can provide a fake stream :)
45
46
    public $searchConditions = [
47
        'Caption' => [
48
            'qualifiers' => ['any','caption']
49
            ,'points' => 2
50
            ,'sql' => 'Caption LIKE "%%%s%%"',
51
        ]
52
        ,'CaptionLike' => [
53
            'qualifiers' => ['caption-like']
54
            ,'points' => 2
55
            ,'sql' => 'Caption LIKE "%s"',
56
        ]
57
        ,'CaptionNot' => [
58
            'qualifiers' => ['caption-not']
59
            ,'points' => 2
60
            ,'sql' => 'Caption NOT LIKE "%%%s%%"',
61
        ]
62
        ,'CaptionNotLike' => [
63
            'qualifiers' => ['caption-not-like']
64
            ,'points' => 2
65
            ,'sql' => 'Caption NOT LIKE "%s"',
66
        ],
67
    ];
68
69
    private ?ServerRequest $request;
70
71 25
    public function handle(ServerRequestInterface $request): ResponseInterface
72
    {
73 25
        $this->request = $request;
74
75
        // handle json response mode
76 25
        if ($this->peekPath() == 'json') {
77 11
            $this->shiftPath();
78 11
            $this->responseBuilder = JsonBuilder::class;
79
        }
80
81
        // handle action
82 25
        switch ($action = $this->shiftPath()) {
83
84 25
            case 'upload':
85
                {
86 7
                    return $this->handleUploadRequest();
87
                }
88
89 18
            case 'open':
90
                {
91
                    $mediaID = $this->shiftPath();
92
93
                    return $this->handleMediaRequest($mediaID);
94
                }
95
96 18
            case 'download':
97
                {
98 1
                    $mediaID = $this->shiftPath();
99 1
                    if ($filename = $this->shiftPath()) {
100
                        $filename = urldecode($filename);
101
                    }
102 1
                    return $this->handleDownloadRequest($mediaID, $filename);
103
                }
104
105 17
            case 'info':
106
                {
107 1
                    $mediaID = $this->shiftPath();
108
109 1
                    return $this->handleInfoRequest($mediaID);
110
                }
111
112 16
            case 'caption':
113
                {
114
                    $mediaID = $this->shiftPath();
115
116
                    return $this->handleCaptionRequest($mediaID);
117
                }
118
119 16
            case 'delete':
120
                {
121
                    $mediaID = $this->shiftPath();
122
                    return $this->handleDeleteRequest($mediaID);
123
                }
124
125 16
            case 'thumbnail':
126
                {
127 3
                    return $this->handleThumbnailRequest();
128
                }
129
130
            case false:
131 12
            case '':
132 12
            case 'browse':
133
                {
134 1
                    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
135
                        return $this->handleUploadRequest();
136
                    }
137
138 1
                    return $this->handleBrowseRequest();
139
                }
140
141
            default:
142
                {
143 12
                    if (ctype_digit($action)) {
144 12
                        return $this->handleMediaRequest($action);
145
                    } else {
146
                        return parent::handleRecordsRequest($action);
147
                    }
148
                }
149
        }
150
    }
151
152
153 7
    public function handleUploadRequest($options = []): ResponseInterface
154
    {
155 7
        $this->checkUploadAccess();
156
157 7
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
158
            // init options
159 5
            $options = array_merge([
160 5
                'fieldName' => $this->uploadFileFieldName,
161 5
            ], $options);
162
163
164
            // check upload
165 5
            if (empty($_FILES[$options['fieldName']])) {
166 1
                return $this->throwUploadError('You did not select a file to upload');
167
            }
168
169
            // handle upload errors
170 4
            if ($_FILES[$options['fieldName']]['error'] != UPLOAD_ERR_OK) {
171 4
                switch ($_FILES[$options['fieldName']]['error']) {
172 4
                    case UPLOAD_ERR_NO_FILE:
173 1
                        return $this->throwUploadError('You did not select a file to upload');
174
175 3
                    case UPLOAD_ERR_INI_SIZE:
176 2
                    case UPLOAD_ERR_FORM_SIZE:
177 1
                        return $this->throwUploadError('Your file exceeds the maximum upload size. Please try again with a smaller file.');
178
179 2
                    case UPLOAD_ERR_PARTIAL:
180 1
                        return $this->throwUploadError('Your file was only partially uploaded, please try again.');
181
182
                    default:
183 1
                        return $this->throwUploadError('There was an unknown problem while processing your upload, please try again.');
184
                }
185
            }
186
187
            // init caption
188
            if (!isset($options['Caption'])) {
189
                if (!empty($_REQUEST['Caption'])) {
190
                    $options['Caption'] = $_REQUEST['Caption'];
191
                } else {
192
                    $options['Caption'] = preg_replace('/\.[^.]+$/', '', $_FILES[$options['fieldName']]['name']);
193
                }
194
            }
195
196
            // create media
197
            try {
198
                $Media = Media::createFromUpload($_FILES[$options['fieldName']]['tmp_name'], $options);
199
            } catch (Exception $e) {
200
                return $this->throwUploadError($e->getMessage());
201
            }
202 2
        } elseif ($_SERVER['REQUEST_METHOD'] == 'PUT') {
203 2
            $put = fopen(static::$inputStream, 'r'); // open input stream
204
205 2
            $tmp = tempnam('/tmp', 'dvr');  // use PHP to make a temporary file
206 2
            $fp = fopen($tmp, 'w'); // open write stream to temp file
207
208
            // write
209 2
            while ($data = fread($put, 1024)) {
210 2
                fwrite($fp, $data);
211
            }
212
213
            // close handles
214 2
            fclose($fp);
215 2
            fclose($put);
216
217
            // create media
218
            try {
219 2
                $Media = Media::createFromFile($tmp, $options);
220
            } catch (Exception $e) {
221 2
                return $this->throwUploadError('The file you uploaded is not of a supported media format');
222
            }
223
        } else {
224
            return $this->respond('upload');
225
        }
226
227
        // assign context
228 2
        if (!empty($_REQUEST['ContextClass']) && !empty($_REQUEST['ContextID'])) {
229
            if (!is_subclass_of($_REQUEST['ContextClass'], ActiveRecord::class)
230
                || !in_array($_REQUEST['ContextClass']::getStaticRootClass(), Media::$fields['ContextClass']['values'])
231
                || !is_numeric($_REQUEST['ContextID'])) {
232
                return $this->throwUploadError('Context is invalid');
233
            } elseif (!$Media->Context = $_REQUEST['ContextClass']::getByID($_REQUEST['ContextID'])) {
234
                return $this->throwUploadError('Context class not found');
235
            }
236
237
            $Media->save();
238
        }
239
240 2
        return $this->respond('uploadComplete', [
241 2
            'success' => (bool)$Media
242 2
            ,'data' => $Media,
243 2
        ]);
244
    }
245
246 2
    public function respondRangeNotSatisfiable(string $responseID, int $start, int $end, int $size): Response
247
    {
248 2
        $this->responseBuilder = EmptyBuilder::class;
249
250 2
        return $this->respondEmpty($responseID)
251 2
            ->withStatus(416) // Range Not Satisfiable
252 2
            ->withHeader('Content-Range', "bytes $start-$end/$size");
253
    }
254
255
    /**
256
     * Set caching headers
257
     *
258
     * @param Response $response
259
     * @return Response
260
     */
261 10
    public function setCache(Response $response): Response
262
    {
263 10
        $expires = 60*60*24*365;
264 10
        return $response->withHeader('Cache-Control', "public, max-age= $expires")
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response->withHe...der('Pragma', 'public') could return the type GuzzleHttp\Psr7\MessageTrait&object which includes types incompatible with the type-hinted return Divergence\Responders\Response. Consider adding an additional type-check to rule them out.
Loading history...
265 10
        ->withHeader('Expires', gmdate('D, d M Y H:i:s \G\M\T', time()+$expires))
266 10
        ->withHeader('Pragma', 'public');
267
    }
268
269 9
    public function respondWithMedia(Media $Media, $variant, $responseID, $responseData = []): ResponseInterface
270
    {
271 9
        if ($this->responseBuilder != MediaBuilder::class) {
272
            throw new Exception('Media responses require MediaBuilder for putting together a response.');
273
        }
274 9
        $className = $this->responseBuilder;
275 9
        $responseBuilder = new $className($responseID, $responseData);
276
277 9
        $responseBuilder->setContentType($Media->MIMEType);
278
279
280 9
        $size = filesize($responseID);
281 9
        $length = $size;
282 9
        $start = 0;
283 9
        $end = $size - 1;
284
285
        // interpret range requests
286 9
        $_server = $this->request->getServerParams();
0 ignored issues
show
Bug introduced by
The method getServerParams() does not exist on null. ( Ignorable by Annotation )

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

286
        /** @scrutinizer ignore-call */ 
287
        $_server = $this->request->getServerParams();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
287 9
        if (!empty($_server['HTTP_RANGE'])) {
288 7
            $chunkStart = $start;
0 ignored issues
show
Unused Code introduced by
The assignment to $chunkStart is dead and can be removed.
Loading history...
289 7
            $chunkEnd = $end;
0 ignored issues
show
Unused Code introduced by
The assignment to $chunkEnd is dead and can be removed.
Loading history...
290
291 7
            list(, $range) = explode('=', $_server['HTTP_RANGE'], 2);
292
293
            // comma indicates multiple ranges which we currently do not support
294 7
            if (strpos($range, ',') !== false) {
295 1
                return $this->respondRangeNotSatisfiable($responseID, $start, $end, $size);
296
            }
297
298 6
            if ($range == '-') { // missing range start and end
299 1
                $range = '0-';
300
            }
301
302 6
            $range = explode('-', $range);
303 6
            $chunkStart = $range[0];
304 6
            $chunkEnd = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
305
306
307 6
            $chunkEnd = ($chunkEnd > $end) ? $end : $chunkEnd;
308
309
            // requested content out of bounds
310 6
            if ($chunkStart > $chunkEnd || $chunkStart > $size - 1 || $chunkEnd >= $size) {
311 1
                return $this->respondRangeNotSatisfiable($responseID, $start, $end, $size);
312
            }
313
314 5
            $start = intval($chunkStart);
315 5
            $end = intval($chunkEnd);
316 5
            $length = $end - $start + 1;
317 5
            $responseBuilder->setRange($start, $end, $length);
318
        }
319
320
321 7
        $response = new MediaResponse($responseBuilder);
322 7
        $response = $this->setCache($response);
323
324
        // tell browser ranges are accepted
325 7
        $response = $response->withHeader('Accept-Ranges', 'bytes')
326 7
        // provide a unique ID for this media
327 7
        ->withHeader('ETag', 'media-'.$Media->ID.'-'.$variant);
328
329
        // if partial content provide proper response header
330 7
        if (isset($chunkStart)) {
331 5
            $response = $response->withStatus(206)
0 ignored issues
show
Bug introduced by
It seems like withStatus() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

331
            $response = $response->/** @scrutinizer ignore-call */ withStatus(206)
Loading history...
332 5
            ->withHeader('Content-Range', "bytes $start-$end/$size")
333 5
            ->withHeader('Content-Length', $length);
334
        } else {
335
            // range
336 2
            $filesize = filesize($Media->getFilesystemPath($variant));
337 2
            $end = $filesize - 1;
338 2
            $response = $response->withHeader('Content-Range', 'bytes 0-'.$end.'/'.$filesize)
339 2
                ->withHeader('Content-Length', $filesize);
340
        }
341
342 7
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response could return the type GuzzleHttp\Psr7\MessageTrait&object which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface. Consider adding an additional type-check to rule them out.
Loading history...
343
    }
344
345 3
    public function respondWithThumbnail(Media $Media, $variant, $responseID, $responseData = []): ResponseInterface
346
    {
347 3
        if ($this->responseBuilder != MediaBuilder::class) {
348
            throw new Exception('Media responses require MediaBuilder for putting together a response.');
349
        }
350 3
        $className = $this->responseBuilder;
351 3
        $responseBuilder = new $className($responseID, $responseData);
352
353 3
        $responseBuilder->setContentType($Media->ThumbnailMIMEType);
354
355 3
        $response = new MediaResponse($responseBuilder);
356 3
        $response = $this->setCache($response);
357
358 3
        $response = $response->withHeader('ETag', "media-$Media->ID-$variant")
359 3
            ->withHeader('Content-Length', filesize($responseID));
360
361 3
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response could return the type GuzzleHttp\Psr7\MessageTrait&object which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface. Consider adding an additional type-check to rule them out.
Loading history...
362
    }
363
364 4
    public function respondEmpty($responseID, $responseData = [])
365
    {
366 4
        if ($this->responseBuilder != EmptyBuilder::class) {
367
            throw new Exception('Media responses require MediaBuilder for putting together a response.');
368
        }
369 4
        $className = $this->responseBuilder;
370 4
        $responseBuilder = new $className($responseID, $responseData);
371 4
        $response = new EmptyResponse($responseBuilder);
372 4
        return $response;
373
    }
374
375
376 12
    public function handleMediaRequest($mediaID): ResponseInterface
377
    {
378 12
        if (empty($mediaID)) {
379
            return $this->throwNotFoundError();
380
        }
381
382
        // get media
383
        try {
384 12
            $Media = Media::getById($mediaID);
385
        } catch (Exception $e) {
386
            return $this->throwUnauthorizedError();
387
        }
388
389 12
        if (!$Media) {
390 1
            return $this->throwNotFoundError();
391
        }
392
393 11
        if (!$this->checkReadAccess($Media)) {
394
            return $this->throwNotFoundError();
395
        }
396
397
398 11
        $_server = $this->request->getServerParams();
399
400 11
        if (isset($_server['HTTP_ACCEPT'])) {
401
            if ($_server['HTTP_ACCEPT'] == 'application/json') {
402
                $this->responseBuilder = JsonBuilder::class;
403
            }
404
        }
405
406 11
        if ($this->responseBuilder == JsonBuilder::class) {
407 1
            return $this->respond('media', [
408 1
                'success' => true
409 1
                ,'data' => $Media,
410 1
            ]);
411
        } else {
412
413
            // determine variant
414 10
            if ($variant = $this->shiftPath()) {
415
                if (!$Media->isVariantAvailable($variant)) {
416
                    return $this->throwNotFoundError();
417
                }
418
            } else {
419 10
                $variant = 'original';
420
            }
421
422
            // initialize response
423 10
            $this->responseBuilder = MediaBuilder::class;
424 10
            set_time_limit(0);
425 10
            $filePath = $Media->getFilesystemPath($variant);
426
427
            // media are immutable for a given URL, so no need to actually check anything if the browser wants to revalidate its cache
428 10
            if (!empty($_server['HTTP_IF_NONE_MATCH']) || !empty($_server['HTTP_IF_MODIFIED_SINCE'])) {
429 2
                $this->responseBuilder = EmptyBuilder::class;
430 2
                $response = $this->respondEmpty($filePath);
431 2
                $response->withDefaults(304);
432
433 2
                return $response;
434
            }
435
436
437 8
            return $this->respondWithMedia($Media, $variant, $filePath);
438
        }
439
    }
440
441 1
    public function handleInfoRequest($mediaID): ResponseInterface
442
    {
443 1
        if (empty($mediaID) || !is_numeric($mediaID)) {
444
            $this->throwNotFoundError();
445
        }
446
447
        // get media
448
        try {
449 1
            $Media = Media::getById($mediaID);
450
        } catch (Exception $e) {
451
            return $this->throwUnauthorizedError();
452
        }
453
454 1
        if (!$Media) {
455
            return $this->throwNotFoundError();
456
        }
457
458 1
        if (!$this->checkReadAccess($Media)) {
459
            return $this->throwUnauthorizedError();
460
        }
461
462 1
        return parent::handleRecordRequest($Media);
463
    }
464
465 1
    public function handleDownloadRequest($media_id, $filename = false): ResponseInterface
466
    {
467 1
        if (empty($media_id) || !is_numeric($media_id)) {
468
            $this->throwNotFoundError();
469
        }
470
471
        // get media
472
        try {
473 1
            $Media = Media::getById($media_id);
474
        } catch (Exception $e) {
475
            return $this->throwUnauthorizedError();
476
        }
477
478
479 1
        if (!$Media) {
480
            return $this->throwNotFoundError();
481
        }
482
483 1
        if (!$this->checkReadAccess($Media)) {
484
            return $this->throwUnauthorizedError();
485
        }
486
487 1
        $filePath = $Media->getFilesystemPath('original');
488
489 1
        $this->responseBuilder = MediaBuilder::class;
490 1
        $response = $this->respondWithMedia($Media, 'original', $filePath);
491
492 1
        $response = $response->withHeader('Content-Disposition', 'attachment; filename="'.($filename ? $filename : $filePath).'"');
493
494 1
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response could return the type GuzzleHttp\Psr7\MessageTrait&object which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface. Consider adding an additional type-check to rule them out.
Loading history...
495
    }
496
497
    public function handleCaptionRequest($media_id): ResponseInterface
498
    {
499
        // require authentication
500
        $GLOBALS['Session']->requireAccountLevel('Staff');
501
502
        if (empty($media_id) || !is_numeric($media_id)) {
503
            return $this->throwNotFoundError();
504
        }
505
506
        // get media
507
        try {
508
            $Media = Media::getById($media_id);
509
        } catch (Exception $e) {
510
            return $this->throwUnauthorizedError();
511
        }
512
513
514
        if (!$Media) {
515
            $this->throwNotFoundError();
516
        }
517
518
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
519
            $Media->Caption = $_REQUEST['Caption'];
520
            $Media->save();
521
522
            return $this->respond('mediaCaptioned', [
523
                'success' => true
524
                ,'data' => $Media,
525
            ]);
526
        }
527
528
        return $this->respond('mediaCaption', [
529
            'data' => $Media,
530
        ]);
531
    }
532
533
    public function handleDeleteRequest(ActiveRecord $Record): ResponseInterface
534
    {
535
        // require authentication
536
        $GLOBALS['Session']->requireAccountLevel('Staff');
537
538
        if ($mediaID = $this->peekPath()) {
539
            $mediaIDs = [$mediaID];
540
        } elseif (!empty($_REQUEST['mediaID'])) {
541
            $mediaIDs = [$_REQUEST['mediaID']];
542
        } elseif (is_array($_REQUEST['media'])) {
543
            $mediaIDs = $_REQUEST['media'];
544
        }
545
546
        $deleted = [];
547
        foreach ($mediaIDs as $mediaID) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $mediaIDs does not seem to be defined for all execution paths leading up to this point.
Loading history...
548
            if (!is_numeric($mediaID)) {
549
                continue;
550
            }
551
552
            // get media
553
            $Media = Media::getByID($mediaID);
554
555
            if (!$Media) {
556
                return $this->throwNotFoundError();
557
            }
558
559
            if ($Media->destroy()) {
560
                $deleted[] = $Media;
561
            }
562
        }
563
564
        return $this->respond('mediaDeleted', [
565
            'success' => true
566
            ,'data' => $deleted,
567
        ]);
568
    }
569
570 3
    public function handleThumbnailRequest(Media $Media = null): ResponseInterface
571
    {
572
        // get media
573 3
        if (!$Media) {
574 3
            if (!$mediaID = $this->shiftPath()) {
575
                return $this->throwNotFoundError();
576 3
            } elseif (!$Media = Media::getByID($mediaID)) {
577
                return $this->throwNotFoundError();
578
            }
579
        }
580
581 3
        $_server = $this->request->getServerParams();
582
583
        // thumbnails are immutable for a given URL, so no need to actually check anything if the browser wants to revalidate its cache
584 3
        if (!empty($_server['HTTP_IF_NONE_MATCH']) || !empty($_server['HTTP_IF_MODIFIED_SINCE'])) {
585
            $this->responseBuilder = EmptyBuilder::class;
586
            $response = $this->respondEmpty($Media->ID);
587
            $response->withDefaults(304);
588
589
            return $response;
590
        }
591
592
        // get format
593 3
        if (preg_match('/^(\d+)x(\d+)(x([0-9A-F]{6})?)?$/i', $this->peekPath(), $matches)) {
0 ignored issues
show
Bug introduced by
It seems like $this->peekPath() can also be of type false; however, parameter $subject of preg_match() 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

593
        if (preg_match('/^(\d+)x(\d+)(x([0-9A-F]{6})?)?$/i', /** @scrutinizer ignore-type */ $this->peekPath(), $matches)) {
Loading history...
594 2
            $this->shiftPath();
595 2
            $maxWidth = $matches[1];
596 2
            $maxHeight = $matches[2];
597 2
            $fillColor = !empty($matches[4]) ? $matches[4] : false;
598
        } else {
599 1
            $maxWidth = $this->defaultThumbnailWidth;
600 1
            $maxHeight = $this->defaultThumbnailHeight;
601 1
            $fillColor = false;
602
        }
603
604 3
        if ($this->peekPath() == 'cropped') {
605
            $this->shiftPath();
606
            $cropped = true;
607
        } else {
608 3
            $cropped = false;
609
        }
610
611
        // get thumbnail media
612
        try {
613 3
            $thumbPath = $Media->getThumbnail($maxWidth, $maxHeight, $fillColor, $cropped);
614
615 3
            $this->responseBuilder = MediaBuilder::class;
616 3
            $response = $this->respondWithThumbnail($Media, "$maxWidth-$maxHeight-$fillColor-$cropped", $thumbPath);
617 3
            return $response;
618
        } catch (Exception $e) {
619
            return $this->throwNotFoundError();
620
        }
621
    }
622
623
624 1
    public function handleBrowseRequest($options = [], $conditions = [], $responseID = null, $responseData = []): ResponseInterface
625
    {
626
        // apply tag filter
627 1
        if (!empty($_REQUEST['tag'])) {
628
            // get tag
629
            if (!$Tag = Tag::getByHandle($_REQUEST['tag'])) {
0 ignored issues
show
Bug introduced by
The type Divergence\Controllers\Tag was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
630
                return $this->throwNotFoundError();
631
            }
632
633
            $conditions[] = 'ID IN (SELECT ContextID FROM tag_items WHERE TagID = '.$Tag->ID.' AND ContextClass = "Product")';
634
        }
635
636
637
        // apply context filter
638 1
        if (!empty($_REQUEST['ContextClass'])) {
639
            $conditions['ContextClass'] = $_REQUEST['ContextClass'];
640
        }
641
642 1
        if (!empty($_REQUEST['ContextID']) && is_numeric($_REQUEST['ContextID'])) {
643
            $conditions['ContextID'] = $_REQUEST['ContextID'];
644
        }
645
646 1
        return parent::handleBrowseRequest($options, $conditions, $responseID, $responseData);
647
    }
648
649
650
651
    public function handleMediaDeleteRequest(): ResponseInterface
652
    {
653
        // sanity check
654
        if (empty($_REQUEST['media']) || !is_array($_REQUEST['media'])) {
655
            return $this->throwNotFoundError();
656
        }
657
658
        // retrieve photos
659
        $media_array = [];
660
        foreach ($_REQUEST['media'] as $media_id) {
661
            if (!is_numeric($media_id)) {
662
                return $this->throwNotFoundError();
663
            }
664
665
            if ($Media = Media::getById($media_id)) {
666
                $media_array[$Media->ID] = $Media;
667
668
                if (!$this->checkWriteAccess($Media)) {
669
                    return $this->throwUnauthorizedError();
670
                }
671
            }
672
        }
673
674
        // delete
675
        $deleted = [];
676
        foreach ($media_array as $media_id => $Media) {
677
            if ($Media->delete()) {
678
                $deleted[] = $media_id;
679
            }
680
        }
681
682
        return $this->respond('mediaDeleted', [
683
            'success' => true
684
            ,'deleted' => $deleted,
685
        ]);
686
    }
687
688 7
    public function checkUploadAccess()
689
    {
690 7
        return true;
691
    }
692
693 5
    public function throwUploadError($error): ResponseInterface
694
    {
695 5
        return $this->respond('error', [
696 5
            'success' => false,
697 5
            'failed' => [
698 5
                'errors'	=>	$error,
699 5
            ],
700 5
        ]);
701
    }
702
}
703