• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

AJenbo / agcms / 20972494104

13 Jan 2026 09:02PM UTC coverage: 53.72% (+0.2%) from 53.541%
20972494104

push

github

AJenbo
Upgrade codebase to support PHP 8.5 compatibility

247 of 340 new or added lines in 40 files covered. (72.65%)

6 existing lines in 5 files now uncovered.

2780 of 5175 relevant lines covered (53.72%)

13.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/application/inc/Http/Controllers/Admin/ExplorerController.php
1
<?php
2

3
namespace App\Http\Controllers\Admin;
4

5
use App\Exceptions\Exception;
6
use App\Exceptions\InvalidInput;
7
use App\Http\Request;
8
use App\Models\CustomPage;
9
use App\Models\File;
10
use App\Models\InterfaceRichText;
11
use App\Models\Newsletter;
12
use App\Models\Page;
13
use App\Models\Requirement;
14
use App\Render;
15
use App\Services\ConfigService;
16
use App\Services\DbService;
17
use App\Services\FileService;
18
use App\Services\ImageService;
19
use App\Services\OrmService;
20
use App\Services\RenderService;
21
use App\Services\UploadHandler;
22
use Symfony\Component\HttpFoundation\BinaryFileResponse;
23
use Symfony\Component\HttpFoundation\File\UploadedFile;
24
use Symfony\Component\HttpFoundation\JsonResponse;
25
use Symfony\Component\HttpFoundation\ParameterBag;
26
use Symfony\Component\HttpFoundation\Response;
27
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
28

29
class ExplorerController extends AbstractAdminController
30
{
31
    private FileService $fileService;
32

33
    public function __construct()
34
    {
35
        $this->fileService = new FileService();
×
36
    }
37

38
    /**
39
     * Show the file manager.
40
     */
41
    public function index(Request $request): Response
42
    {
NEW
43
        $currentDir = valstring($request->cookies->get('admin_dir', '/images'));
×
44

45
        $data = [
×
46
            'returnType' => $request->get('return', ''),
×
47
            'returnid'   => $request->get('returnid', ''),
×
48
            'bgcolor'    => ConfigService::getString('bgcolor'),
×
49
            'dirs'       => $this->fileService->getRootDirs($currentDir),
×
50
        ];
51

52
        return $this->render('admin/explorer', $data);
×
53
    }
54

55
    /**
56
     * Render subfolder.
57
     */
58
    public function folders(Request $request): JsonResponse
59
    {
60
        $path = $request->query->get('path');
×
61
        if (!is_string($path)) {
×
62
            $path = '';
×
63
        }
64
        $move = $request->query->getBoolean('move');
×
NEW
65
        $currentDir = valstring($request->cookies->get('admin_dir', '/images'));
×
66

67
        $html = app(RenderService::class)->render(
×
68
            'admin/partial-listDirs',
69
            [
70
                'dirs' => $this->fileService->getSubDirs($path, $currentDir),
×
71
                'move' => $move,
72
            ]
73
        );
74

75
        return new JsonResponse(['id' => $path, 'html' => $html]);
×
76
    }
77

78
    /**
79
     * Display a list of files in the selected folder.
80
     *
81
     * @todo only output json, let fronend generate html and init objects
82
     */
83
    public function files(Request $request): JsonResponse
84
    {
85
        $path = $request->query->get('path');
×
86
        if (!is_string($path)) {
×
87
            $path = '';
×
88
        }
89
        $returnType = $request->query->get('return');
×
90
        if (!is_string($returnType)) {
×
91
            $returnType = '';
×
92
        }
93

94
        $this->fileService->checkPermittedPath($path);
×
95

96
        $app = app();
×
97

98
        $files = scandir($app->basePath($path)) ?: [];
×
99
        natcasesort($files);
×
100

101
        $html = '';
×
102
        $fileData = [];
×
103
        foreach ($files as $fileName) {
×
104
            if ('.' === mb_substr($fileName, 0, 1) || is_dir($app->basePath($path . '/' . $fileName))) {
×
105
                continue;
×
106
            }
107

108
            $filePath = $path . '/' . $fileName;
×
109
            $file = File::getByPath($filePath);
×
110
            if (!$file) {
×
111
                $file = File::fromPath($filePath)->save();
×
112
            }
113

114
            $html .= $this->fileService->filehtml($file, $returnType);
×
115
            $fileData[] = $this->fileService->fileAsArray($file);
×
116
        }
117

118
        return new JsonResponse(['id' => 'files', 'html' => $html, 'files' => $fileData]);
×
119
    }
120

121
    /**
122
     * Search for files.
123
     */
124
    public function search(Request $request): JsonResponse
125
    {
126
        $returnType = $request->query->get('return');
×
127
        if (!is_string($returnType)) {
×
128
            $returnType = '';
×
129
        }
130
        $qpath = $request->query->get('qpath');
×
131
        if (!is_string($qpath)) {
×
132
            $qpath = '';
×
133
        }
134
        $qalt = $request->query->get('qalt');
×
135
        if (!is_string($qalt)) {
×
136
            $qalt = '';
×
137
        }
138

139
        $qtype = $request->query->get('qtype');
×
140
        if (!is_string($qtype)) {
×
141
            $qtype = '';
×
142
        }
143

144
        $html = '';
×
145
        $fileData = [];
×
146
        $query = $this->buildSearchQuery($qpath, $qalt, $qtype);
×
147

148
        $files = app(OrmService::class)->getByQuery(File::class, $query);
×
149
        foreach ($files as $file) {
×
150
            if ('unused' !== $qtype || !$file->isInUse()) {
×
151
                $html .= $this->fileService->filehtml($file, $returnType);
×
152
                $fileData[] = $this->fileService->fileAsArray($file);
×
153
            }
154
        }
155

156
        return new JsonResponse(['id' => 'files', 'html' => $html, 'files' => $fileData]);
×
157
    }
158

159
    private function buildSearchQuery(string $qpath, string $qalt, string $qtype): string
160
    {
161
        $db = app(DbService::class);
×
162
        $qpath = $db->escapeWildcards($qpath);
×
163
        $qalt = $db->escapeWildcards($qalt);
×
164

165
        $sqlMime = $this->getMimeClause($qtype);
×
166

167
        //Generate search query
168
        $sql = ' FROM `files`';
×
169
        if ($qpath || $qalt || $sqlMime) {
×
170
            $sql .= ' WHERE ';
×
171
            if ($qpath || $qalt) {
×
172
                $sql .= '(';
×
173
            }
174
            if ($qpath) {
×
175
                $sql .= 'MATCH(path) AGAINST(' . $db->quote($qpath) . ')>0';
×
176
            }
177
            if ($qpath && $qalt) {
×
178
                $sql .= ' OR ';
×
179
            }
180
            if ($qalt) {
×
181
                $sql .= 'MATCH(alt) AGAINST(' . $db->quote($qalt) . ')>0';
×
182
            }
183
            if ($qpath) {
×
184
                $sql .= ' OR `path` LIKE ' . $db->quote('%' . $qpath . '%');
×
185
            }
186
            if ($qalt) {
×
187
                $sql .= ' OR `alt` LIKE ' . $db->quote('%' . $qalt . '%');
×
188
            }
189
            if ($qpath || $qalt) {
×
190
                $sql .= ')';
×
191
            }
192
            if (($qpath || $qalt) && !empty($sqlMime)) {
×
193
                $sql .= ' AND ';
×
194
            }
195
            if (!empty($sqlMime)) {
×
196
                $sql .= $sqlMime;
×
197
            }
198
        }
199

200
        $sqlSelect = '';
×
201
        if ($qpath || $qalt) {
×
202
            $sqlSelect .= ', ';
×
203
            if ($qpath && $qalt) {
×
204
                $sqlSelect .= '(';
×
205
            }
206
            if ($qpath) {
×
207
                $sqlSelect .= 'MATCH(path) AGAINST(' . $db->quote($qpath) . ')';
×
208
            }
209
            if ($qpath && $qalt) {
×
210
                $sqlSelect .= ' + ';
×
211
            }
212
            if ($qalt) {
×
213
                $sqlSelect .= 'MATCH(alt) AGAINST(' . $db->quote($qalt) . ')';
×
214
            }
215
            if ($qpath && $qalt) {
×
216
                $sqlSelect .= ')';
×
217
            }
218
            $sqlSelect .= ' AS score';
×
219
            $sql = $sqlSelect . $sql;
×
220
            $sql .= ' ORDER BY `score` DESC';
×
221
        }
222

223
        return 'SELECT *' . $sql;
×
224
    }
225

226
    private function getMimeClause(string $qtype): string
227
    {
228
        switch ($qtype) {
229
            case 'image':
×
230
                return "mime IN('image/jpeg', 'image/png', 'image/gif')";
×
231
            case 'imagefile':
×
232
                return "mime LIKE 'image/%' AND mime NOT IN('image/jpeg', 'image/png', 'image/gif')";
×
233
            case 'video':
×
234
                return "mime LIKE 'video/%'";
×
235
            case 'audio':
×
236
                return "mime LIKE 'audio/%'";
×
237
            case 'text':
×
238
                return "(
×
239
                    mime IN(
240
                        'application/pdf',
241
                        'application/msword',
242
                        'application/vnd.ms-%',
243
                        'application/vnd.openxmlformats-officedocument.%',
244
                        'application/vnd.oasis.opendocument.%'
245
                )";
246
            case 'compressed':
×
247
                return "mime = 'application/zip'";
×
248
            default:
249
                return '';
×
250
        }
251
    }
252

253
    public function fileDelete(Request $request, int $id): JsonResponse
254
    {
255
        $file = app(OrmService::class)->getOne(File::class, $id);
×
256
        if ($file) {
×
257
            if ($file->isInUse()) {
×
258
                throw new InvalidInput(_('The file can not be deleted because it is in use.'), Response::HTTP_LOCKED);
×
259
            }
260

261
            $file->delete();
×
262
        }
263

264
        return new JsonResponse(['id' => $id]);
×
265
    }
266

267
    /**
268
     * Create new folder.
269
     */
270
    public function folderCreate(Request $request): JsonResponse
271
    {
272
        $path = $request->query->get('path');
×
273
        if (!is_string($path)) {
×
274
            $path = '';
×
275
        }
276
        $name = $request->query->get('name');
×
277
        if (!is_string($name)) {
×
278
            $name = '';
×
279
        }
280
        $name = $this->fileService->cleanFileName($name);
×
281
        $newPath = $path . '/' . $name;
×
282

283
        $this->fileService->createFolder($newPath);
×
284

285
        return new JsonResponse([]);
×
286
    }
287

288
    /**
289
     * Endpoint for deleting a folder.
290
     */
291
    public function folderDelete(Request $request): JsonResponse
292
    {
293
        $path = $request->query->get('path');
×
294
        if (!is_string($path)) {
×
295
            $path = '';
×
296
        }
297
        $this->fileService->deleteFolder($path);
×
298

299
        return new JsonResponse([]);
×
300
    }
301

302
    public function fileView(Request $request, int $id): Response
303
    {
304
        $file = app(OrmService::class)->getOne(File::class, $id);
×
305
        if (!$file) {
×
306
            throw new InvalidInput(_('File not found.'), Response::HTTP_NOT_FOUND);
×
307
        }
308

309
        $template = 'admin/popup-image';
×
310
        if (0 === mb_strpos($file->getMime(), 'video/')) {
×
311
            $template = 'admin/popup-video';
×
312
        } elseif (0 === mb_strpos($file->getMime(), 'audio/')) {
×
313
            $template = 'admin/popup-audio';
×
314
        }
315

316
        return $this->render($template, ['file' => $file]);
×
317
    }
318

319
    /**
320
     * Check if a file already exists.
321
     */
322
    public function fileExists(Request $request): JsonResponse
323
    {
324
        $path = $request->query->get('path');
×
325
        if (!is_string($path)) {
×
326
            $path = '';
×
327
        }
328
        $type = $request->query->get('type');
×
329
        if (!is_string($type)) {
×
330
            $type = '';
×
331
        }
332

333
        $pathinfo = pathinfo($path);
×
334

335
        if ('image' === $type) {
×
336
            $pathinfo['extension'] = 'jpg';
×
337
        } elseif ('lineimage' === $type) {
×
338
            $pathinfo['extension'] = 'png';
×
339
        } elseif (empty($pathinfo['extension'])) {
×
340
            $pathinfo['extension'] = 'jpg';
×
341
        }
342

343
        $path = ($pathinfo['dirname'] ?? '') . '/' . $this->fileService->cleanFileName($pathinfo['filename']);
×
344
        $fullPath = app()->basePath($path);
×
345
        $fullPath .= '.' . $pathinfo['extension'];
×
346

347
        return new JsonResponse(['exists' => is_file($fullPath), 'name' => basename($fullPath)]);
×
348
    }
349

350
    /**
351
     * Update image description.
352
     *
353
     * @todo make db fixer check for missing alt="" in <img>
354
     */
355
    public function fileDescription(Request $request, int $id): JsonResponse
356
    {
357
        $orm = app(OrmService::class);
×
358

359
        $file = $orm->getOne(File::class, $id);
×
360
        if (!$file) {
×
361
            throw new InvalidInput(_('File not found.'), Response::HTTP_NOT_FOUND);
×
362
        }
363

364
        $description = $request->getRequestString('description') ?? '';
×
365
        $file->setDescription($description)->save();
×
366

367
        $db = app(DbService::class);
×
368

369
        foreach ([Page::class, CustomPage::class, Requirement::class, Newsletter::class] as $className) {
×
370
            $richTexts = $orm->getByQuery(
×
371
                $className,
372
                'SELECT * FROM `' . $className::TABLE_NAME
373
                    . '` WHERE `text` LIKE ' . $db->quote('%="' . $file->getPath() . '"%')
×
374
            );
375
            $this->updateAltInHtml($richTexts, $file);
×
376
        }
377

378
        return new JsonResponse(['id' => $id, 'description' => $description]);
×
379
    }
380

381
    /**
382
     * Update alt text for images in HTML text.
383
     *
384
     * @param InterfaceRichText[] $richTexts
385
     *
386
     * @throws Exception
387
     */
388
    private function updateAltInHtml(array $richTexts, File $file): void
389
    {
390
        foreach ($richTexts as $richText) {
×
391
            $html = $richText->getHtml();
×
392
            $html = preg_replace(
×
393
                [
394
                    '/(<img[^>]+src="' . preg_quote($file->getPath(), '/') . '"[^>]+alt=")[^"]*("[^>]*>)/iu',
×
395
                    '/(<img[^>]+alt=")[^"]*("[^>]+src="' . preg_quote($file->getPath(), '/') . '"[^>]*>)/iu',
×
396
                ],
397
                '\1' . htmlspecialchars($file->getDescription(), ENT_COMPAT | ENT_XHTML) . '\2',
×
398
                $html
399
            );
400
            if (null === $html) {
×
401
                throw new Exception('preg_replace failed');
×
402
            }
403
            $richText->setHtml($html)->save();
×
404
        }
405
    }
406

407
    /**
408
     * File viwer.
409
     */
410
    public function fileMoveDialog(Request $request, int $id): Response
411
    {
NEW
412
        $currentDir = valstring($request->cookies->get('admin_dir', '/images'));
×
413

414
        $file = app(OrmService::class)->getOne(File::class, $id);
×
415
        if (!$file) {
×
416
            throw new InvalidInput(_('File not found.'), Response::HTTP_NOT_FOUND);
×
417
        }
418

419
        $data = [
×
420
            'file' => $file,
421
            'dirs' => $this->fileService->getRootDirs($currentDir),
×
422
        ];
423

424
        return $this->render('admin/file-move', $data);
×
425
    }
426

427
    /**
428
     * Upload dialog.
429
     */
430
    public function fileUploadDialog(Request $request): Response
431
    {
432
        $maxbyte = min(
×
433
            $this->fileService->returnBytes(ini_get('post_max_size') ?: '0'),
×
434
            $this->fileService->returnBytes(ini_get('upload_max_filesize') ?: '0')
×
435
        );
436

437
        $data = [
×
438
            'maxbyte'   => $maxbyte,
439
            'activeDir' => $request->get('path'),
×
440
        ];
441

442
        return $this->render('admin/file-upload', $data);
×
443
    }
444

445
    public function fileUpload(Request $request): Response
446
    {
447
        /** @var ?UploadedFile */
448
        $uploadedFile = $request->files->get('upload');
×
449
        if (!$uploadedFile) {
×
450
            throw new InvalidInput(_('No file received.'));
×
451
        }
452

NEW
453
        $currentDir = valstring($request->cookies->get('admin_dir', '/images'));
×
454
        $targetDir = $request->getRequestString('dir') ?? $currentDir;
×
455
        $destinationType = $request->getRequestString('type') ?? '';
×
456
        $description = $request->getRequestString('alt') ?? '';
×
457

458
        $uploadHandler = new UploadHandler($targetDir);
×
459
        $file = $uploadHandler->process($uploadedFile, $destinationType, $description);
×
460

461
        $data = [
×
462
            'uploaded' => 1,
463
            'fileName' => basename($file->getPath()),
×
464
            'url'      => $file->getPath(),
×
465
            'width'    => $file->getWidth(),
×
466
            'height'   => $file->getHeight(),
×
467
        ];
468

469
        return new JsonResponse($data);
×
470
    }
471

472
    /**
473
     * Rename or relocate file.
474
     *
475
     * @throws Exception
476
     */
477
    public function fileRename(Request $request, int $id): JsonResponse
478
    {
479
        try {
480
            $file = app(OrmService::class)->getOne(File::class, $id);
×
481
            if (!$file) {
×
482
                throw new InvalidInput(_('File not found.'), Response::HTTP_NOT_FOUND);
×
483
            }
484

485
            $pathinfo = pathinfo($file->getPath());
×
486

487
            $dir = $request->getRequestString('dir') ?? ($pathinfo['dirname'] ?? '');
×
488
            $filename = $request->getRequestString('name') ?? $pathinfo['filename'];
×
489
            $filename = $this->fileService->cleanFileName($filename);
×
490
            $overwrite = $request->request->getBoolean('overwrite');
×
491

492
            $ext = $pathinfo['extension'] ?? '';
×
493
            $ext = $ext ? '.' . $ext : '';
×
494
            $newPath = $dir . '/' . $filename . $ext;
×
495

496
            if ($file->getPath() === $newPath) {
×
497
                return new JsonResponse(['id' => $id, 'filename' => $filename, 'path' => $file->getPath()]);
×
498
            }
499

500
            if (!$filename) {
×
501
                throw new InvalidInput(_('The name is invalid.'));
×
502
            }
503

504
            $this->fileService->checkPermittedTargetPath($newPath);
×
505

506
            $existingFile = File::getByPath($newPath);
×
507
            if ($existingFile) {
×
508
                if ($existingFile->isInUse()) {
×
509
                    throw new InvalidInput(_('File already exists.'));
×
510
                }
511

512
                if (!$overwrite) {
×
513
                    return new JsonResponse([
×
514
                        'yesno' => _(
×
515
                            'A file with the same name already exists. Would you like to replace the existing file?'
516
                        ),
517
                        'id'    => $id,
518
                    ]);
519
                }
520

521
                $existingFile->delete();
×
522
            }
523

524
            if (!$file->move($newPath)) {
×
525
                throw new Exception(_('An error occurred with the file operations.'));
×
526
            }
527
        } catch (InvalidInput $exception) {
×
528
            return new JsonResponse(
×
529
                ['error' => ['message' => $exception->getMessage()], 'id' => $id],
×
530
                Response::HTTP_BAD_REQUEST
531
            );
532
        }
533

534
        return new JsonResponse(['id' => $id, 'filename' => $filename, 'path' => $newPath]);
×
535
    }
536

537
    /**
538
     * Rename directory.
539
     *
540
     * @throws Exception
541
     */
542
    public function folderRename(Request $request): JsonResponse
543
    {
544
        $path = $request->getRequestString('path') ?? '';
×
545
        $name = $request->getRequestString('name') ?? '';
×
546
        $name = $this->fileService->cleanFileName($name);
×
547
        $overwrite = $request->request->getBoolean('overwrite');
×
548

549
        $dirname = pathinfo($path, PATHINFO_DIRNAME);
×
550
        $newPath = $dirname . '/' . $name;
×
551

552
        if ($path === $newPath) {
×
553
            return new JsonResponse(['filename' => $name, 'path' => $path, 'newPath' => $newPath]);
×
554
        }
555

556
        try {
557
            if (!$name) {
×
558
                throw new InvalidInput(_('The name is invalid.'));
×
559
            }
560

561
            $this->fileService->checkPermittedTargetPath($path);
×
562

563
            $app = app();
×
564

565
            if (file_exists($app->basePath($newPath))) {
×
566
                if (!$overwrite) {
×
567
                    return new JsonResponse([
×
568
                        'yesno' => _(
×
569
                            'A file with the same name already exists. Would you like to replace the existing file?'
570
                        ),
571
                        'path'  => $path,
572
                    ]);
573
                }
574

575
                $this->fileService->deleteFolder($newPath);
×
576
            }
577

578
            if (!rename($app->basePath($path), $app->basePath($newPath))) {
×
579
                throw new Exception(_('An error occurred with the file operations.'));
×
580
            }
581
        } catch (InvalidInput $exception) {
×
582
            return new JsonResponse(
×
583
                ['error' => ['message' => $exception->getMessage()], 'path' => $path],
×
584
                Response::HTTP_BAD_REQUEST
585
            );
586
        }
587

588
        $this->fileService->replaceFolderPaths($path, $newPath);
×
589

590
        return new JsonResponse(['filename' => $name, 'path' => $path, 'newPath' => $newPath]);
×
591
    }
592

593
    /**
594
     * Image editing window.
595
     */
596
    public function imageEditWidget(Request $request, int $id): Response
597
    {
598
        $file = app(OrmService::class)->getOne(File::class, $id);
×
599
        if (!$file) {
×
600
            throw new InvalidInput(_('File not found.'), Response::HTTP_NOT_FOUND);
×
601
        }
602

603
        $mode = $request->get('mode');
×
604

605
        $fileName = '';
×
606
        if ('thb' === $mode) {
×
607
            $fileName = pathinfo($file->getPath(), PATHINFO_FILENAME) . '-thb';
×
608
        }
609

610
        $data = [
×
611
            'textWidth'   => ConfigService::getInt('text_width'),
×
612
            'thumbWidth'  => ConfigService::getInt('thumb_width'),
×
613
            'thumbHeight' => ConfigService::getInt('thumb_height'),
×
614
            'mode'        => $mode,
615
            'fileName'    => $fileName,
616
            'file'        => $file,
617
        ];
618

619
        return $this->render('admin/image-edit', $data);
×
620
    }
621

622
    /**
623
     * Dynamic image.
624
     *
625
     * @throws Exception
626
     */
627
    public function image(Request $request, int $id): Response
628
    {
629
        $file = app(OrmService::class)->getOne(File::class, $id);
×
630
        if (!$file) {
×
631
            throw new InvalidInput(_('File not found.'), Response::HTTP_NOT_FOUND);
×
632
        }
633

634
        $path = $file->getPath();
×
635

636
        $noCache = $request->query->getBoolean('noCache');
×
637

638
        $app = app();
×
639

640
        $timestamp = filemtime($app->basePath($path));
×
641
        if (false === $timestamp) {
×
642
            throw new Exception('File not found.', Response::HTTP_NOT_FOUND);
×
643
        }
644

645
        if (!$noCache) {
×
646
            $response = $this->cachedResponse(null, $timestamp, 2592000);
×
647
            if ($response->isNotModified($request)) {
×
648
                return $response;
×
649
            }
650
        }
651

652
        $image = $this->createImageServiceFomRequest($request->query, $app->basePath($path));
×
653
        if ($image->isNoOp()) {
×
654
            return redirect($path, Response::HTTP_MOVED_PERMANENTLY);
×
655
        }
656

657
        $targetPath = tempnam(sys_get_temp_dir(), 'image');
×
658
        if (!$targetPath) {
×
659
            throw new Exception('Failed to create temporary file');
×
660
        }
661

662
        $type = 'jpeg';
×
663
        if ('image/jpeg' !== $file->getMime()) {
×
664
            $type = 'png';
×
665
        }
666

667
        $image->processImage($targetPath, $type);
×
668

669
        $response = new BinaryFileResponse($targetPath);
×
670
        $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE, pathinfo($path, PATHINFO_BASENAME));
×
671

672
        return $this->cachedResponse($response, $timestamp, 2592000);
×
673
    }
674

675
    /**
676
     * Process an image.
677
     */
678
    public function imageSave(Request $request, int $id): Response
679
    {
680
        $file = app(OrmService::class)->getOne(File::class, $id);
×
681
        if (!$file) {
×
682
            throw new InvalidInput(_('File not found.'), Response::HTTP_NOT_FOUND);
×
683
        }
684

685
        $path = $file->getPath();
×
686
        $fullPath = app()->basePath($path);
×
687

688
        $image = $this->createImageServiceFomRequest($request->request, $fullPath);
×
689
        if ($image->isNoOp()) {
×
690
            return $this->createImageResponse($file);
×
691
        }
692
        if ($file->isInUse(true)) {
×
693
            throw new InvalidInput(_('Image can not be changed as it used in a text.'), Response::HTTP_LOCKED);
×
694
        }
695

696
        $type = 'jpeg';
×
697
        $mime = 'image/jpeg';
×
698
        if ('image/jpeg' !== $file->getMime()) {
×
699
            $type = 'png';
×
700
            $mime = 'image/png';
×
701
        }
702

703
        $image->processImage($fullPath, $type);
×
704

705
        $file->setWidth($image->getWidth())
×
706
            ->setHeight($image->getHeight())
×
707
            ->setMime($mime)
×
708
            ->setSize(filesize($fullPath) ?: 0)
×
709
            ->save();
×
710

711
        return $this->createImageResponse($file);
×
712
    }
713

714
    /**
715
     * Generate a thumbnail image from an existing image.
716
     */
717
    public function imageSaveThumb(Request $request, int $id): Response
718
    {
719
        $file = app(OrmService::class)->getOne(File::class, $id);
×
720
        if (!$file) {
×
721
            throw new InvalidInput(_('File not found.'), Response::HTTP_NOT_FOUND);
×
722
        }
723

724
        $path = $file->getPath();
×
725

726
        $app = app();
×
727

728
        $image = $this->createImageServiceFomRequest($request->request, $app->basePath($path));
×
729
        if ($image->isNoOp()) {
×
730
            return $this->createImageResponse($file);
×
731
        }
732

733
        $type = 'jpeg';
×
734
        $ext = 'jpg';
×
735
        $mime = 'image/jpeg';
×
736
        if ('image/jpeg' !== $file->getMime()) {
×
737
            $type = 'png';
×
738
            $ext = 'png';
×
739
            $mime = 'image/png';
×
740
        }
741

742
        $pathInfo = pathinfo($path);
×
743
        $newPath = ($pathInfo['dirname'] ?? '') . '/' . $pathInfo['filename'] . '-thb.' . $ext;
×
744

745
        if (File::getByPath($newPath)) {
×
746
            throw new InvalidInput(_('Thumbnail already exists.'));
×
747
        }
748
        $image->processImage($app->basePath($newPath), $type);
×
749

750
        $newFile = File::fromPath($newPath);
×
751
        $newFile->setDescription($file->getDescription())->save();
×
752

753
        return $this->createImageResponse($newFile);
×
754
    }
755

756
    /**
757
     * Create an image service from a path and the request parameteres.
758
     *
759
     * @param ParameterBag<string, int> $parameterBag
760
     */
761
    private function createImageServiceFomRequest(ParameterBag $parameterBag, string $path): ImageService
762
    {
763
        $image = new ImageService($path);
×
764
        $image->setCrop(
×
765
            $parameterBag->getInt('cropX'),
×
766
            $parameterBag->getInt('cropY'),
×
767
            $parameterBag->getInt('cropW'),
×
768
            $parameterBag->getInt('cropH')
×
769
        );
770
        $image->setScale($parameterBag->getInt('maxW'), $parameterBag->getInt('maxH'));
×
771
        $image->setFlip($parameterBag->getInt('flip'));
×
772
        $image->setRotate($parameterBag->getInt('rotate'));
×
773

774
        return $image;
×
775
    }
776

777
    /**
778
     * Create an image response for the image editor.
779
     */
780
    private function createImageResponse(File $file): JsonResponse
781
    {
782
        return new JsonResponse([
×
783
            'id'     => $file->getId(),
×
784
            'path'   => $file->getPath(),
×
785
            'width'  => $file->getWidth(),
×
786
            'height' => $file->getHeight(),
×
787
        ]);
788
    }
789
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc