Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
67.42% covered (warning)
67.42%
60 / 89
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
PathManager
67.42% covered (warning)
67.42%
60 / 89
33.33% covered (danger)
33.33%
2 / 6
52.22
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 path
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 persist
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
7
 prepareUrlPath
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 createUrlPaths
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
5.01
 saveUrlPaths
38.71% covered (danger)
38.71%
12 / 31
0.00% covered (danger)
0.00%
0 / 1
22.74
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Node;
6
7use Celema\Quma\Database;
8use Cosray\Exception\RuntimeException;
9use Cosray\Locale;
10use Cosray\Locales;
11use Cosray\Uid;
12
13class PathManager
14{
15    public function __construct(
16        public readonly ReservedPaths $reserved,
17        private readonly Uid $uid = new Uid(Uid::ALPHABET_LOWERCASE_WORD_SAFE, 13),
18    ) {}
19
20    /** @param array<mixed> $rawData */
21    public function path(array $rawData, ?Locale $locale, Locale $requestLocale): string
22    {
23        $paths = $rawData['paths'];
24
25        if (!$locale) {
26            $locale = $requestLocale;
27        }
28
29        while ($locale) {
30            if (isset($paths[$locale->id])) {
31                return $paths[$locale->id];
32            }
33
34            $locale = $locale->fallback();
35        }
36
37        throw new RuntimeException('No url path found');
38    }
39
40    /** @param array<mixed> $data */
41    public function persist(
42        Database $db,
43        array $data,
44        int $editor,
45        int $nodeId,
46        Locales $locales,
47    ): void {
48        $noPathsGiven = true;
49
50        foreach ($data['paths'] ?? [] as $path) {
51            if (!$path) {
52                continue;
53            }
54
55            $noPathsGiven = false;
56            break;
57        }
58
59        if ($noPathsGiven) {
60            $data['paths'] = $data['generatedPaths'];
61        }
62
63        $this->reserved->assertFree($data['paths']);
64
65        $defaultLocale = $locales->getDefault();
66        $defaultPath = trim($data['paths'][$defaultLocale->id] ?? '');
67
68        if (!$defaultPath) {
69            throw new RuntimeException(sprintf(
70                'The URL path for the main language "%s" must be set',
71                $defaultLocale->title,
72            ));
73        }
74
75        $currentPaths = array_column($db->nodes->getPaths(['node' => $nodeId])->all(), 'path', 'locale');
76
77        if ($currentPaths) {
78            $baseStructure = [];
79
80            foreach ($locales as $locale) {
81                $baseStructure[$locale->id] = '';
82            }
83
84            $this->saveUrlPaths(
85                $db,
86                array_merge($baseStructure, $currentPaths),
87                $data['paths'],
88                $editor,
89                $nodeId,
90            );
91        } else {
92            $this->createUrlPaths($db, $data['paths'], $editor, $nodeId);
93        }
94    }
95
96    private function prepareUrlPath(Database $db, string $path): string
97    {
98        if (!str_starts_with($path, '/')) {
99            $path = '/' . $path;
100        }
101
102        $db->nodes->deleteInactivePath(['path' => $path])->run();
103
104        return $path;
105    }
106
107    /** @param array<string, mixed> $paths */
108    private function createUrlPaths(Database $db, array $paths, int $editor, int $node): void
109    {
110        $alreadyPersisted = [];
111
112        foreach ($paths as $locale => $path) {
113            if (!$path) {
114                continue;
115            }
116
117            $this->prepareUrlPath($db, $path);
118
119            if (in_array($path, $alreadyPersisted, true)) {
120                continue;
121            }
122
123            if ($db->nodes->pathExists(['path' => $path])->first()) {
124                $path = $path . '-' . $this->uid->generate(5);
125            }
126
127            $db->nodes->savePath([
128                'node' => $node,
129                'path' => $path,
130                'locale' => $locale,
131                'editor' => $editor,
132            ])->run();
133
134            $alreadyPersisted[] = $path;
135        }
136    }
137
138    /**
139     * @param array<string, mixed> $currentPaths
140     * @param array<string, mixed> $paths
141     */
142    private function saveUrlPaths(
143        Database $db,
144        array $currentPaths,
145        array $paths,
146        int $editor,
147        int $node,
148    ): void {
149        $alreadyPersisted = [];
150
151        foreach ($currentPaths as $locale => $currentPath) {
152            $newPath = trim($paths[$locale] ?? '');
153
154            if ($newPath) {
155                $newPath = $this->prepareUrlPath($db, $newPath);
156
157                if ($currentPath) {
158                    if ($currentPath === $newPath) {
159                        $alreadyPersisted[] = $newPath;
160
161                        continue;
162                    }
163
164                    $db->nodes->deactivatePath([
165                        'path' => $currentPath,
166                        'locale' => $locale,
167                        'editor' => $editor,
168                    ])->run();
169                }
170
171                if (in_array($newPath, $alreadyPersisted, true)) {
172                    continue;
173                }
174
175                if ($db->nodes->pathExists(['path' => $newPath])->first()) {
176                    $newPath = $newPath . '-' . $this->uid->generate(5);
177                }
178
179                $db->nodes->savePath([
180                    'node' => $node,
181                    'path' => $newPath,
182                    'locale' => $locale,
183                    'editor' => $editor,
184                ])->run();
185
186                $alreadyPersisted[] = $newPath;
187            } else {
188                if ($currentPath) {
189                    $db->nodes->deactivatePath([
190                        'path' => $currentPath,
191                        'locale' => $locale,
192                        'editor' => $editor,
193                    ])->run();
194                }
195            }
196        }
197    }
198}