Passed
Pull Request — master (#7178)
by
unknown
21:38 queued 12:23
created

DocumentUsageAction::formatBytes()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 14
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Chamilo\CoreBundle\Controller\Api;
6
7
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
8
use Chamilo\CourseBundle\Repository\CDocumentRepository;
9
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
10
use Symfony\Component\HttpFoundation\JsonResponse;
11
use Symfony\Component\HttpKernel\Attribute\AsController;
12
13
#[AsController]
14
final class DocumentUsageAction extends AbstractController
15
{
16
    /**
17
     * Fallback quota (MB) when the course quota is empty or not set.
18
     */
19
    private const DEFAULT_QUOTA_MB = 100;
20
21
    public function __construct(
22
        private readonly CourseRepository $courseRepository,
23
        private readonly CDocumentRepository $documentRepository,
24
    ) {}
25
26
    public function __invoke($cid): JsonResponse
27
    {
28
        $courseId = (int) $cid;
29
30
        $course = $this->courseRepository->find($courseId);
31
        if (null === $course) {
32
            return new JsonResponse(['error' => 'Course not found'], 404);
33
        }
34
35
        // Resolve quota in MB safely (avoid "($x * ...) ?? fallback").
36
        $quotaMb = (int) ($course->getDiskQuota() ?? 0);
37
        if ($quotaMb <= 0) {
38
            $quotaMb = self::DEFAULT_QUOTA_MB;
39
        }
40
41
        $totalQuotaBytes = $quotaMb * 1024 * 1024;
42
43
        // Compute usage using repository logic (deduplicated).
44
        $usage = $this->documentRepository->getDocumentUsageBreakdownByCourse($course);
45
46
        $bytesCourse = (int) ($usage['course'] ?? 0);
47
        $bytesSessions = (int) ($usage['sessions'] ?? 0);
48
        $bytesGroups = (int) ($usage['groups'] ?? 0);
49
50
        $usedBytes = (int) ($usage['used'] ?? ($bytesCourse + $bytesSessions + $bytesGroups));
51
52
        // Keep the pie meaningful even when used > quota.
53
        $denomBytes = max($totalQuotaBytes, $usedBytes, 1);
54
55
        $availableBytes = max($totalQuotaBytes - $usedBytes, 0);
56
57
        $labels = [];
58
        $data = [];
59
60
        if ($bytesCourse > 0) {
61
            $labels[] = get_lang('Course').' ('.$this->formatBytes($bytesCourse).')';
62
            $data[] = $this->pct($bytesCourse, $denomBytes);
63
        }
64
65
        if ($bytesSessions > 0) {
66
            $labels[] = get_lang('Session').' ('.$this->formatBytes($bytesSessions).')';
67
            $data[] = $this->pct($bytesSessions, $denomBytes);
68
        }
69
70
        if ($bytesGroups > 0) {
71
            $labels[] = get_lang('Group').' ('.$this->formatBytes($bytesGroups).')';
72
            $data[] = $this->pct($bytesGroups, $denomBytes);
73
        }
74
75
        $labels[] = get_lang('Available space').' ('.$this->formatBytes($availableBytes).')';
76
        $data[] = $this->pct($availableBytes, $denomBytes);
77
78
        return new JsonResponse([
79
            'datasets' => [
80
                ['data' => $data],
81
            ],
82
            'labels' => $labels,
83
        ]);
84
    }
85
86
    private function pct(int $part, int $total): float
87
    {
88
        if ($total <= 0) {
89
            return 0.0;
90
        }
91
92
        return round(($part / $total) * 100, 2);
93
    }
94
95
    private function formatBytes(int $bytes): string
96
    {
97
        // Simple dependency-free formatter for API responses.
98
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
99
        $size = (float) max($bytes, 0);
100
        $i = 0;
101
102
        $max = count($units) - 1;
103
        while ($size >= 1024 && $i < $max) {
104
            $size /= 1024;
105
            $i++;
106
        }
107
108
        return round($size, 2).' '.$units[$i];
109
    }
110
}
111