StatisticRepository::getOrCreate()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 10
c 0
b 0
f 0
dl 0
loc 17
ccs 12
cts 12
cp 1
rs 9.9332
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Repository;
6
7
use Application\Enum\Site;
8
use Application\Model\Statistic;
9
use Application\Model\User;
10
use Generator;
11
use InvalidArgumentException;
12
13
/**
14
 * @extends AbstractRepository<Statistic>
15
 */
16
class StatisticRepository extends AbstractRepository implements \Ecodev\Felix\Repository\LimitedAccessSubQuery
17
{
18 1
    public function getOrCreate(Site $site): Statistic
19
    {
20 1
        $date = date('Y-m');
21 1
        $statistic = $this->getAclFilter()->runWithoutAcl(fn () => $this->findOneBy([
22 1
            'date' => $date,
23 1
            'site' => $site->value,
24 1
        ]));
25
26 1
        if (!$statistic) {
27 1
            $statistic = new Statistic();
28 1
            $statistic->setSite($site);
29 1
            $statistic->setDate($date);
30
31 1
            $this->getEntityManager()->persist($statistic);
32
        }
33
34 1
        return $statistic;
35
    }
36
37
    /**
38
     * Returns pure SQL to get ID of all objects that are accessible to given user.
39
     *
40
     * @param User $user
41
     */
42
    public function getAccessibleSubQuery(?\Ecodev\Felix\Model\User $user): string
43
    {
44
        if (!$user || $user->getRole() !== User::ROLE_ADMINISTRATOR) {
45
            return '-1';
46
        }
47
48
        return 'SELECT id FROM statistic WHERE site = ' . $this->getEntityManager()->getConnection()->quote($user->getSite()->value);
49
    }
50
51 8
    public function getExtraStatistics(Site $site, string $period, ?User $user): array
52
    {
53 8
        $userSuffix = $user ? ' par ' . $user->getLogin() : '';
54
55 8
        $extraStatistics = [
56 8
            'cardCreation' => [
57 8
                'tables' => [
58 8
                    $this->getCardVisibility($site, $period, $user, true),
59 8
                    $this->getCardDescription($site, $period, $user, true),
60 8
                    $this->getCardGeolocation($site, $period, $user, true),
61 8
                ],
62 8
                'chart' => $this->oneChart($site, $period, 'card', true, 'visibility', 'Création de fiche' . $userSuffix, $user),
63 8
            ],
64 8
            'cardUpdate' => [
65 8
                'tables' => [
66 8
                    $this->getCardVisibility($site, $period, $user, false),
67 8
                    $this->getCardDescription($site, $period, $user, false),
68 8
                    $this->getCardGeolocation($site, $period, $user, false),
69 8
                ],
70 8
                'chart' => $this->oneChart($site, $period, 'card', false, 'visibility', 'Modification de fiche' . $userSuffix, $user),
71 8
            ],
72 8
            'userCreation' => [
73 8
                'tables' => [
74 8
                    $this->getUserType($site, $period, true),
75 8
                    $this->getUserRole($site, $period, true),
76 8
                ],
77 8
                'chart' => $this->oneChart($site, $period, 'user', true, 'type', 'Création d\'utilisateur'),
78 8
            ],
79 8
            'userUpdate' => [
80 8
                'tables' => [
81 8
                    $this->getUserType($site, $period, false),
82 8
                    $this->getUserRole($site, $period, false),
83 8
                ],
84 8
                'chart' => $this->oneChart($site, $period, 'user', false, 'type', 'Modification d\'utilisateur'),
85 8
            ],
86 8
        ];
87
88 6
        return $extraStatistics;
89
    }
90
91 8
    private function getCardVisibility(Site $site, string $period, ?User $user, bool $isCreation): array
92
    {
93 8
        $periodClause = $this->getPeriodClause($isCreation, $period);
94 6
        $userClause = $this->getUserClause($user, $isCreation);
95
96 6
        $query = "SELECT
97
SUM(CASE WHEN visibility = 'private' THEN 1 ELSE 0 END) AS 'Visible par moi',
98
SUM(CASE WHEN visibility = 'member' THEN 1 ELSE 0 END) AS 'Visible par tous les membres',
99
SUM(CASE WHEN visibility = 'public' THEN 1 ELSE 0 END) AS 'Visible par tout le monde'
100
FROM card
101
WHERE 
102
site = :site
103 6
$periodClause
104 6
$userClause
105 6
";
106
107 6
        return $this->toTableRows($site, 'Visibilité', $query);
108
    }
109
110 8
    private function getPeriodClause(bool $isCreation, string $period): string
111
    {
112 8
        if ($period === 'all') {
113 2
            return ' AND 1=1';
114
        }
115
116 6
        $field = $this->getDateField($isCreation);
117
118 6
        if ($period === 'month') {
119 2
            return " AND $field BETWEEN DATE_FORMAT(CURDATE(), '%Y-%m-01') AND LAST_DAY(CURDATE())";
120
        }
121 4
        if (is_numeric($period)) {
122 2
            return " AND YEAR($field) = $period";
123
        }
124
125 2
        throw new InvalidArgumentException("Invalid period: $period");
126
    }
127
128 2
    private function formatMonth(int $month): string
129
    {
130 2
        return mb_str_pad((string) $month, 2, '0', STR_PAD_LEFT);
131
    }
132
133 8
    private function getDateField(bool $isCreation): string
134
    {
135 8
        return $isCreation ? 'creation_date' : 'update_date';
136
    }
137
138 6
    private function getUserClause(?User $user, bool $isCreation): string
139
    {
140 6
        $field = $isCreation ? 'creator_id' : 'updater_id';
141 6
        if ($user) {
142 3
            return ' AND ' . $field . ' = ' . $user->getId();
143
        }
144
145 6
        return '';
146
    }
147
148 6
    private function toTableRows(Site $site, string $name, string $query): array
149
    {
150 6
        $connection = $this->getEntityManager()->getConnection();
151 6
        $params = [
152 6
            'site' => $site->value,
153 6
        ];
154
155 6
        $record = $connection->executeQuery($query, $params)->fetchAssociative();
156
157 6
        $result = [];
158 6
        foreach ($record as $key => $v) {
159 6
            $result[] = [
160 6
                'name' => $key,
161 6
                'value' => (int) $v,
162 6
            ];
163
        }
164
165 6
        return [
166 6
            'name' => $name,
167 6
            'rows' => $result,
168 6
        ];
169
    }
170
171 6
    private function getCardDescription(Site $site, string $period, ?User $user, bool $isCreation): array
172
    {
173 6
        $periodClause = $this->getPeriodClause($isCreation, $period);
174 6
        $userClause = $this->getUserClause($user, $isCreation);
175
176 6
        $query = "SELECT
177
SUM(CASE WHEN expanded_name != '' THEN 1 ELSE 0 END) AS 'Avec titre étendu',
178
SUM(CASE WHEN expanded_name = '' THEN 1 ELSE 0 END) AS 'Sans titre étendu'
179
FROM card
180
WHERE 
181
site = :site
182 6
$periodClause
183 6
$userClause
184 6
";
185
186 6
        return $this->toTableRows($site, 'Titre étendu', $query);
187
    }
188
189 6
    private function getCardGeolocation(Site $site, string $period, ?User $user, bool $isCreation): array
190
    {
191 6
        $periodClause = $this->getPeriodClause($isCreation, $period);
192 6
        $userClause = $this->getUserClause($user, $isCreation);
193
194 6
        $query = "SELECT
195
SUM(CASE WHEN location IS NOT NULL THEN 1 ELSE 0 END) AS 'Géolocalisées',
196
SUM(CASE WHEN location IS NULL THEN 1 ELSE 0 END) AS 'Sans géolocalisation'
197
FROM card
198
WHERE 
199
site = :site
200 6
$periodClause
201 6
$userClause
202 6
";
203
204 6
        return $this->toTableRows($site, 'Géolocalisation', $query);
205
    }
206
207 6
    private function oneChart(Site $site, string $period, string $table, bool $isCreation, string $field2, string $name, ?User $user = null): array
208
    {
209 6
        $count = $this->countCardByMonth($site, $period, $table, $isCreation, $field2, $user);
210 6
        $countByDate = $this->groupByDateAndGroup($count, $period);
211 6
        $categories = array_keys($countByDate);
212 6
        $series = [];
213
214 6
        $total = [];
215 6
        foreach ($countByDate as $byDate) {
216 4
            foreach ($byDate as $d) {
217
                $group = $d['grouping'];
218
219
                if (!array_key_exists($group, $series)) {
220
                    $total[$group] = 0;
221
                }
222
223
                $total[$group] += $d['count'];
224
225
                if (!array_key_exists($group, $series)) {
226
                    $series[$group] = [
227
                        'name' => $group,
228
                        'data' => [],
229
                    ];
230
                }
231
232
                $series[$group]['data'][] = (int) $d['count'];
233
            }
234
        }
235
236 6
        $rows = [];
237 6
        $superTotal = 0;
238 6
        foreach ($total as $key => $t) {
239
            $superTotal += $t;
240
            $rows[] = ['name' => $key, 'value' => $t];
241
        }
242
243 6
        $data = [
244 6
            'name' => $name,
245 6
            'categories' => $categories,
246 6
            'series' => array_values($series),
247 6
        ];
248
249 6
        return $data;
250
    }
251
252 6
    private function countCardByMonth(Site $site, string $period, string $table, bool $isCreation, string $groupingField, ?User $user = null): array
253
    {
254 6
        $field = $this->getDateField($isCreation);
255 6
        $connection = $this->getEntityManager()->getConnection();
256 6
        $month = "DATE_FORMAT($field, '%Y-%m')";
257 6
        $periodClause = $this->getPeriodClause($isCreation, $period);
258 6
        $userClause = $this->getUserClause($user, $isCreation);
259
260 6
        $query = "SELECT $month AS date, $groupingField AS grouping, COUNT($field) AS count
261 6
FROM $table
262
WHERE 
263 6
$field IS NOT NULL
264
AND site = :site
265 6
$periodClause
266 6
$userClause
267 6
GROUP BY $month, $groupingField 
268 6
ORDER BY $month, $groupingField ASC
269 6
";
270
271 6
        $params = [
272 6
            'site' => $site->value,
273 6
        ];
274
275 6
        $result = $connection->executeQuery($query, $params)->fetchAllAssociative();
276
277 6
        return $result;
278
    }
279
280 6
    private function groupByDateAndGroup(array $count, string $period): array
281
    {
282 6
        $result = [];
283 6
        $groups = [];
284 6
        foreach ($count as $d) {
285
            if (!array_key_exists($d['date'], $result)) {
286
                $result[$d['date']] = [];
287
            }
288
289
            $groups[] = $d['grouping'];
290
            $result[$d['date']][$d['grouping']] = $d;
291
        }
292
293
        // If year is selected, fill gap for months without data
294 6
        $groups = array_unique($groups);
295 6
        if ($period === 'month') {
296 2
            $first = date('Y-m');
297 2
            $last = $first;
298 4
        } elseif (is_numeric($period)) {
299 2
            $first = $period . '-01';
300 2
            $last = $period . '-12';
301
        } else {
302 2
            $dates = array_keys($result);
303 2
            $first = reset($dates);
304 2
            $last = end($dates);
305
        }
306
307 6
        if ($first && $last) {
308 4
            foreach ($this->months($first, $last) as $date) {
309 4
                if (!array_key_exists($date, $result)) {
310 4
                    $result[$date] = [];
311
                }
312
313 4
                foreach ($groups as $group) {
314
                    if (!array_key_exists($group, $result[$date])) {
315
                        $result[$date][$group] = [
316
                            'date' => $date,
317
                            'count' => 0,
318
                            'grouping' => $group,
319
                        ];
320
                    }
321
                }
322
            }
323
        }
324
325 6
        ksort($result);
326
327 6
        return $result;
328
    }
329
330 4
    private function months(string $first, string $last): Generator
331
    {
332 4
        $date = $first;
333
334 4
        [$year, $month] = explode('-', $date);
335 4
        $year = (int) $year;
336 4
        $month = (int) $month;
337
338 4
        while ($date !== $last) {
339 2
            yield $date;
340
341 2
            if ($month === 12) {
342
                $month = 1;
343
                ++$year;
344
            } else {
345 2
                ++$month;
346
            }
347 2
            $date = $year . '-' . $this->formatMonth($month);
348
        }
349
350 4
        yield $date;
351
    }
352
353 6
    private function getUserType(Site $site, string $period, bool $isCreation): array
354
    {
355 6
        $periodClause = $this->getPeriodClause($isCreation, $period);
356 6
        $query = "SELECT
357
SUM(CASE WHEN type = 'aai' THEN 1 ELSE 0 END) AS 'AAI',
358
SUM(CASE WHEN type = 'default' THEN 1 ELSE 0 END) AS 'Externe',
359
SUM(CASE WHEN type = 'legacy' THEN 1 ELSE 0 END) AS 'Legacy'
360
FROM user
361
WHERE 
362
site = :site
363 6
$periodClause
364 6
";
365
366 6
        return $this->toTableRows($site, 'Type', $query);
367
    }
368
369 6
    private function getUserRole(Site $site, string $period, bool $isCreation): array
370
    {
371 6
        $periodClause = $this->getPeriodClause($isCreation, $period);
372 6
        $query = "SELECT
373
SUM(CASE WHEN role = 'student' THEN 1 ELSE 0 END) AS 'Etudiant',
374
SUM(CASE WHEN role = 'junior' THEN 1 ELSE 0 END) AS 'Etudiant junior',
375
SUM(CASE WHEN role = 'senior' THEN 1 ELSE 0 END) AS 'Senior',
376
SUM(CASE WHEN role = 'administrator' THEN 1 ELSE 0 END) AS 'Administrateur'
377
FROM user
378
WHERE 
379
site = :site
380 6
$periodClause
381 6
";
382
383 6
        return $this->toTableRows($site, 'Rôles', $query);
384
    }
385
}
386